Skip to content

Commit c884b30

Browse files
authored
Merge pull request #249 from LabKey/fb_merge_23.7_to_develop
Merge discvr-23.7 to develop
2 parents 0f63095 + 6e854e3 commit c884b30

File tree

13 files changed

+199
-42
lines changed

13 files changed

+199
-42
lines changed

SequenceAnalysis/src/org/labkey/sequenceanalysis/run/variant/SplitVcfBySamplesStep.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ public void performAdditionalMergeTasks(SequenceOutputHandler.JobContext ctx, Pi
9595
{
9696
job.getLogger().info("Merging additional track VCFs");
9797
File inputVCF = ((SequenceJob)getPipelineCtx().getJob()).getInputFiles().get(0);
98-
List<File> firstJobOutputs = findProducedVcfs(inputVCF, new File(ctx.getWorkingDirectory(), orderedJobDirs.get(0)));
98+
List<File> firstJobOutputs = findProducedVcfs(inputVCF, new File(ctx.getSourceDirectory(), orderedJobDirs.get(0)));
9999
job.getLogger().info("total VCFs found in job dir: " + firstJobOutputs.size());
100100
if (firstJobOutputs.isEmpty())
101101
{
102-
throw new PipelineJobException("No VCFs found in folder: " + new File(ctx.getWorkingDirectory(), orderedJobDirs.get(0)));
102+
throw new PipelineJobException("No VCFs found in folder: " + new File(ctx.getSourceDirectory(), orderedJobDirs.get(0)));
103103
}
104104

105105
for (File fn : firstJobOutputs)
106106
{
107107
List<File> toConcat = orderedJobDirs.stream().map(jobDir -> {
108-
File f = new File(new File(getPipelineCtx().getWorkingDirectory(), jobDir), fn.getName());
108+
File f = new File(new File(getPipelineCtx().getSourceDirectory(), jobDir), fn.getName());
109109
if (!f.exists())
110110
{
111111
throw new IllegalStateException("Missing file: " + f.getPath());

jbrowse/package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jbrowse/package.json

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,55 +13,53 @@
1313
"test": "cross-env NODE_ENV=test jest",
1414
"prepareCli": "rimraf ./buildCli && rimraf ./resources/external/jb-cli && npm install @jbrowse/cli@1.7.4 --prefix ./buildCli",
1515
"jb-pkg": "npm run prepareCli && npx pkg --outdir=./resources/external/jb-cli ./buildCli/node_modules/@jbrowse/cli && rimraf ./buildCli"
16-
},
17-
"peerDependencies": {
18-
1916
},
2017
"dependencies": {
18+
"@gmod/vcf": "^5.0.10",
2119
"@jbrowse/core": "^2.6.2",
22-
"@jbrowse/plugin-variants": "^2.6.2",
23-
"@jbrowse/plugin-svg": "^2.6.2",
2420
"@jbrowse/plugin-linear-genome-view": "^2.6.2",
21+
"@jbrowse/plugin-svg": "^2.6.2",
22+
"@jbrowse/plugin-variants": "^2.6.2",
2523
"@jbrowse/react-linear-genome-view": "^2.6.2",
2624
"@labkey/api": "^1.22.0",
27-
"@gmod/vcf": "^5.0.10",
25+
"@labkey/components": "^2.355.0",
26+
"@mui/x-data-grid": "^6.0.1",
2827
"assert": "^2.0.0",
2928
"browserify-zlib": "^0.2.0",
3029
"buffer": "^6.0.3",
30+
"child_process": "^1.0.2",
31+
"fs": "^0.0.1-security",
3132
"jquery": "^3.7.0",
3233
"jspdf": "^2.5.1",
3334
"jspdf-autotable": "^3.5.31",
34-
"@mui/x-data-grid": "^6.0.1",
3535
"node-polyfill-webpack-plugin": "2.0.1",
36-
"@labkey/components": "^2.355.0",
37-
"uuid": "^9.0.0",
3836
"path-browserify": "^1.0.1",
3937
"pkg": "^5.8.1",
4038
"react-data-grid": "7.0.0-beta.10",
4139
"react-google-charts": "^4.0.1",
40+
"react-hot-loader": "^4.13.1",
41+
"react-select": "^5.7.4",
4242
"regenerator-runtime": "^0.13.11",
4343
"stream-browserify": "^3.0.0",
4444
"typescript": "^5.1.6",
4545
"util": "^0.12.5",
46+
"uuid": "^9.0.0",
4647
"vm-browserify": "^1.1.2",
47-
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz",
48-
"fs": "^0.0.1-security",
49-
"child_process": "^1.0.2",
50-
"react-hot-loader": "^4.13.1"
48+
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
5149
},
5250
"devDependencies": {
5351
"@labkey/build": "6.10.0",
5452
"@types/jest": "^29.0.0",
55-
"@types/jquery": "^3.0.0",
5653
"@types/jexl": "^2.3.1",
54+
"@types/jquery": "^3.0.0",
5755
"@types/node": "^18.17.1",
56+
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
5857
"enzyme": "^3.11.0",
5958
"jest": "^29.0.0",
6059
"jest-cli": "^29.0.0",
6160
"jest-mock": "^29.0.0",
62-
"ts-jest": "^29.0.0",
6361
"rimraf": "^3.0.2",
64-
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0"
62+
"ts-jest": "^29.0.0"
6563
},
6664
"jest": {
6765
"globals": {

jbrowse/src/client/JBrowse/VariantSearch/components/FilterForm.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
22
import TextField from '@mui/material/TextField';
33
import Button from '@mui/material/Button';
44
import Select from '@mui/material/Select';
5+
import AsyncSelect from 'react-select/async';
56
import MenuItem from '@mui/material/MenuItem';
67
import FormControl from '@mui/material/FormControl';
78
import InputLabel from '@mui/material/InputLabel';
@@ -131,6 +132,10 @@ const FilterForm = (props: FilterFormProps ) => {
131132
filters.forEach((filter, index) => {
132133
highlightedInputs[index] = { field: false, operator: false, value: false };
133134

135+
filter.field = filter.field ?? '';
136+
filter.operator = filter.operator ?? '';
137+
filter.value = filter.value ?? '';
138+
134139
if (filter.field === '') {
135140
highlightedInputs[index].field = true;
136141
}
@@ -139,7 +144,9 @@ const FilterForm = (props: FilterFormProps ) => {
139144
highlightedInputs[index].operator = true;
140145
}
141146

142-
if (filter.value === '') {
147+
if (filter.operator === 'is empty' || filter.operator === 'is not empty') {
148+
filter.value = '';
149+
} else if (filter.value === '') {
143150
highlightedInputs[index].value = true;
144151
}
145152
});
@@ -208,8 +215,8 @@ const FilterForm = (props: FilterFormProps ) => {
208215
{filters.map((filter, index) => (
209216
<FilterRow key={index} >
210217
<FormControlMinWidth sx={ highlightedInputs[index]?.field ? highlightedSx : null }>
211-
<InputLabel id="field-label">Field</InputLabel>
212-
<Select
218+
<InputLabel id="field-label">Field</InputLabel>
219+
<Select
213220
labelId="field-label"
214221
value={filter.field}
215222
onChange={(event) =>
@@ -266,9 +273,33 @@ const FilterForm = (props: FilterFormProps ) => {
266273
{allowedGroupNames?.map((gn) => (
267274
<MenuItem value={gn} key={gn}>{gn}</MenuItem>
268275
))}
269-
270276
</Select>
271277
</FormControlMinWidth>
278+
) : fieldTypeInfo.find(obj => obj.name === filter.field)?.allowableValues?.length > 10 ? (
279+
<FormControlMinWidth sx={ highlightedInputs[index]?.value ? highlightedSx : null } >
280+
<AsyncSelect
281+
id={`value-select-${index}`}
282+
inputId={`value-select-${index}`}
283+
aria-labelledby={`value-select-${index}`}
284+
menuPortalTarget={document.body}
285+
menuPosition={'fixed'}
286+
isDisabled={filter.operator === "is empty" || filter.operator === "is not empty"}
287+
menuShouldBlockScroll={true}
288+
styles={{menuPortal: base => ({...base, zIndex: 9999})}}
289+
isMulti={fieldTypeInfo.find(obj => obj.name === filter.field)?.isMultiValued}
290+
loadOptions={(inputValue, callback) => {
291+
const fieldInfo = fieldTypeInfo.find(obj => obj.name === filter.field);
292+
293+
callback(
294+
(fieldInfo?.allowableValues || [])
295+
.filter(value => value.toLowerCase().includes(inputValue.toLowerCase()))
296+
.map(value => ({label: value, value}))
297+
);
298+
}}
299+
onChange={(selected) => handleFilterChange(index, "value", selected?.length > 0 ? selected.map(s => s.value).join(',') : undefined)}
300+
value={filter.value ? filter.value.split(',').map(value => ({label: value, value})) : undefined}
301+
/>
302+
</FormControlMinWidth>
272303
) : fieldTypeInfo.find(obj => obj.name === filter.field)?.allowableValues?.length > 0 ? (
273304
<FormControlMinWidth sx={ highlightedInputs[index]?.value ? highlightedSx : null } >
274305
<InputLabel id="value-select-label">Value</InputLabel>
@@ -316,6 +347,7 @@ const FilterForm = (props: FilterFormProps ) => {
316347
<Button
317348
onClick={handleSubmit}
318349
type="submit"
350+
className="filter-form-select-button"
319351
variant="contained"
320352
color="primary"
321353
>

jbrowse/src/client/JBrowse/VariantSearch/components/VariantTableWidget.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,17 @@ const VariantTableWidget = observer(props => {
463463
<div style={{ marginBottom: "10px", display: "flex", alignItems: "center" }}>
464464
<div style={{ flex: 1 }}>
465465
{filters.map((filter, index) => {
466+
if ((filter as any).field && ((filter as any).operator === "is empty" || (filter as any).operator === "is not empty") && !(filter as any).value) {
467+
return (
468+
<Button
469+
key={index}
470+
onClick={() => setFilterModalOpen(true)}
471+
style={{ border: "1px solid gray", margin: "5px" }}
472+
>
473+
{`${(filter as any).field} ${(filter as any).operator}`}
474+
</Button>
475+
);
476+
}
466477
if ((filter as any).field == "" || (filter as any).operator == "" || (filter as any).value == "" ) {
467478
return (<Button
468479
key={index}
@@ -476,8 +487,7 @@ const VariantTableWidget = observer(props => {
476487
<Button
477488
key={index}
478489
onClick={() => setFilterModalOpen(true)}
479-
style={{ border: "1px solid gray", margin: "5px" }}
480-
>
490+
style={{ border: "1px solid gray", margin: "5px" }} >
481491
{`${(filter as any).field} ${(filter as any).operator} ${(filter as any).value}`}
482492
</Button>
483493
);

jbrowse/src/client/JBrowse/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ export function searchStringToInitialFilters(knownFieldNames: string[]) : Filter
636636
export function getOperatorsForField(fieldObj: FieldModel): string[] {
637637
const stringOperators = ["equals", "does not equal", "contains", "does not contain", "starts with", "ends with", "is empty", "is not empty"];
638638
const variableSamplesType = ["in set", "variable in", "not variable in", "variable in all of", "variable in any of", "not variable in any of", "not variable in one of", "is empty", "is not empty"];
639-
const numericOperators = ["=", "!=", ">", ">=", "<", "<=", "is empty", "is not empty"];
639+
const numericOperators = ["=", "!=", ">", ">=", "<", "<="];
640640
const noneOperators = [];
641641

642642
// This can occur for the blank placeholder field:

jbrowse/src/org/labkey/jbrowse/JBrowseController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ public ApiResponse execute(ResolveVcfFieldsForm form, BindException errors)
861861
if (form.isIncludeDefaultFields())
862862
{
863863
JBrowseFieldUtils.DEFAULT_FIELDS.forEach((key, val) -> ret.put(key, val.toJSON()));
864+
ret.put(JBrowseFieldUtils.VARIABLE_SAMPLES, JBrowseFieldUtils.getVariableSamplesField(null).toJSON());
864865
}
865866

866867
for (String key : form.getInfoKeys())

jbrowse/src/org/labkey/jbrowse/JBrowseFieldUtils.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import htsjdk.variant.vcf.VCFHeader;
55
import htsjdk.variant.vcf.VCFHeaderLineType;
66
import htsjdk.variant.vcf.VCFInfoHeaderLine;
7-
import org.apache.logging.log4j.LogManager;
7+
import org.jetbrains.annotations.Nullable;
88
import org.apache.logging.log4j.Logger;
99
import org.labkey.api.data.Container;
1010
import org.labkey.api.jbrowse.JBrowseFieldDescriptor;
@@ -14,6 +14,7 @@
1414
import org.labkey.jbrowse.model.JsonFile;
1515

1616
import java.io.File;
17+
import java.util.Arrays;
1718
import java.util.LinkedHashMap;
1819
import java.util.Map;
1920

@@ -31,12 +32,12 @@ public class JBrowseFieldUtils
3132
put("ref", new JBrowseFieldDescriptor("ref", "The reference allele", true, true, VCFHeaderLineType.String, 4).label("Ref Allele"));
3233
put("alt", new JBrowseFieldDescriptor("alt", "The alternate allele", true, true, VCFHeaderLineType.String, 5).label("Alt Allele"));
3334
put("genomicPosition", new JBrowseFieldDescriptor("genomicPosition", "", false, true, VCFHeaderLineType.Integer, 6).hidden(true).label("Genomic Position"));
34-
put("variableSamples", new JBrowseFieldDescriptor(VARIABLE_SAMPLES, "All samples with this variant", true, true, VCFHeaderLineType.Character, 7).multiValued(true).label("Samples With Variant"));
3535
}};
3636

3737
public static Map<String, JBrowseFieldDescriptor> getIndexedFields(JsonFile jsonFile, User u, Container c)
3838
{
3939
Map<String, JBrowseFieldDescriptor> ret = new LinkedHashMap<>(DEFAULT_FIELDS);
40+
ret.put(VARIABLE_SAMPLES, getVariableSamplesField(jsonFile));
4041

4142
File vcf = jsonFile.getTrackFile();
4243
if (!vcf.exists())
@@ -66,6 +67,25 @@ public static Map<String, JBrowseFieldDescriptor> getIndexedFields(JsonFile json
6667
return ret;
6768
}
6869

70+
public static JBrowseFieldDescriptor getVariableSamplesField(@Nullable JsonFile jsonFile) {
71+
JBrowseFieldDescriptor field = new JBrowseFieldDescriptor(VARIABLE_SAMPLES, "All samples with this variant", true, true, VCFHeaderLineType.Character, 7).multiValued(true).label("Samples With Variant");
72+
if (jsonFile != null) {
73+
File vcf = jsonFile.getTrackFile();
74+
if (vcf == null || !vcf.exists()) {
75+
String msg = "Unable to find VCF file for track: " + jsonFile.getObjectId();
76+
_log.error(msg + ", expected: " + (vcf == null ? "null" : vcf.getPath()));
77+
return null;
78+
}
79+
80+
try (VCFFileReader reader = new VCFFileReader(vcf)) {
81+
field.allowableValues(reader.getHeader().getSampleNamesInOrder());
82+
}
83+
}
84+
85+
return field;
86+
}
87+
88+
6989
public static JBrowseSession getSession(String sessionId)
7090
{
7191
JBrowseSession session = JBrowseSession.getForId(sessionId);

jbrowse/test/src/org/labkey/test/tests/external/labModules/JBrowseTest.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,13 @@ private void testVariantTableComparators() throws Exception {
15051505
waitAndClick(Locator.tagWithAttributeContaining("button", "aria-label", "Show filters"));
15061506
}
15071507

1508+
private void clearFilterDialog(String filter_text) {
1509+
waitForElement(Locator.tagWithText("button", filter_text)).click();
1510+
waitForElement(Locator.tagWithText("button", "Remove Filter")).click();
1511+
waitAndClick(Locator.tagWithText("button", "Search").index(1));
1512+
waitForElement(Locator.tagWithText("span", "2"));
1513+
}
1514+
15081515
private void testLuceneSearchUI(String sessionId)
15091516
{
15101517
beginAt("/" + getProjectName() + "/jbrowse-jbrowse.view?session=" + sessionId);
@@ -1530,16 +1537,63 @@ private void testLuceneSearchUI(String sessionId)
15301537

15311538
waitForElement(Locator.tagWithClass("input", "MuiInputBase-inputSizeSmall")).sendKeys("C");
15321539

1533-
// TODO: index isnt very stable. we need to differentiate the modal button from the grid button bar:
1534-
waitAndClick(Locator.tagWithText("button", "Search").index(1));
1540+
waitAndClick(Locator.tagWithClass("button", "filter-form-select-button"));
15351541

15361542
// indicates row is filtered:
15371543
waitForElementToDisappear(Locator.tagWithText("span", "2"));
15381544

1539-
// should re-open the dialog
1540-
waitForElement(Locator.tagWithText("button", "ref equals C")).click();
1541-
waitForElement(Locator.tagWithText("button", "Remove Filter")).click();
1542-
waitAndClick(Locator.tagWithText("button", "Search").index(1));
1543-
waitForElement(Locator.tagWithText("span", "2"));
1545+
clearFilterDialog("ref equals C");
1546+
1547+
// VariableSamples variable in m000001
1548+
waitAndClick(Locator.tagWithText("button", "Search"));
1549+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "field-label")).click();
1550+
waitForElement(Locator.tagWithText("li", "Samples With Variant")).click();
1551+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "operator-label")).click();
1552+
waitForElement(Locator.tagWithText("li", "variable in")).click();
1553+
waitForElement(Locator.tagWithId("input", "value-select-0")).sendKeys("m00001");
1554+
waitForElement(Locator.tagWithId("input", "value-select-0")).sendKeys(Keys.ENTER);
1555+
waitAndClick(Locator.tagWithClass("button", "filter-form-select-button"));
1556+
waitForElement(Locator.tagWithText("span", "0.553"));
1557+
1558+
clearFilterDialog("variableSamples variable in m00001");
1559+
1560+
// VariableSamples li usage + variable in all of
1561+
waitAndClick(Locator.tagWithText("button", "Search"));
1562+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "field-label")).click();
1563+
waitForElement(Locator.tagWithText("li", "Samples With Variant")).click();
1564+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "operator-label")).click();
1565+
waitForElement(Locator.tagWithText("li", "variable in all of")).click();
1566+
waitForElement(Locator.tagWithId("input", "value-select-0")).sendKeys("m000");
1567+
waitForElement(Locator.tagWithText("div", "m00005")).click();
1568+
waitForElement(Locator.tagWithId("input", "value-select-0")).sendKeys("m000");
1569+
waitForElement(Locator.tagWithText("div", "m00004")).click();
1570+
waitAndClick(Locator.tagWithClass("button", "filter-form-select-button"));
1571+
waitForElement(Locator.tagWithText("span", "0.3"));
1572+
1573+
clearFilterDialog("variableSamples variable in all of m00005,m00004");
1574+
1575+
// VariableSamples not variable in m05710
1576+
waitAndClick(Locator.tagWithText("button", "Search"));
1577+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "field-label")).click();
1578+
waitForElement(Locator.tagWithText("li", "Samples With Variant")).click();
1579+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "operator-label")).click();
1580+
waitForElement(Locator.tagWithText("li", "not variable in")).click();
1581+
waitForElement(Locator.tagWithId("input", "value-select-0")).sendKeys("m05710");
1582+
waitForElement(Locator.tagWithId("input", "value-select-0")).sendKeys(Keys.ENTER);
1583+
waitAndClick(Locator.tagWithClass("button", "filter-form-select-button"));
1584+
waitForElementToDisappear(Locator.tagWithText("span", "3.277E-4"));
1585+
1586+
clearFilterDialog("variableSamples not variable in m05710");
1587+
1588+
// samples with variant isEmpty
1589+
waitAndClick(Locator.tagWithText("button", "Search"));
1590+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "field-label")).click();
1591+
waitForElement(Locator.tagWithText("li", "Samples With Variant")).click();
1592+
waitForElement(Locator.tagWithAttribute("div", "aria-labelledby", "operator-label")).click();
1593+
waitForElement(Locator.tagWithText("li", "is empty")).click();
1594+
waitAndClick(Locator.tagWithClass("button", "filter-form-select-button"));
1595+
waitForElementToDisappear(Locator.tagWithText("span", "2"));
1596+
1597+
clearFilterDialog("variableSamples is empty");
15441598
}
15451599
}

0 commit comments

Comments
 (0)