gramene-search 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.parcel-cache/83e7562660f7cc15-BundleGraph +0 -0
- package/.parcel-cache/d3a1b9507cb44047-AssetGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/dc1da35000e13623-RequestGraph +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-dc1da35000e13623.txt +2 -2
- package/dist/index.js +87 -91
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bundles/swaggerFields.js +72 -93
- package/src/bundles/views.js +7 -3
- package/src/components/exporter/ExporterView.js +9 -1
- package/src/fieldCatalog.overlay.json +2 -1
package/package.json
CHANGED
|
@@ -2,10 +2,27 @@ import { createAsyncResourceBundle, createSelector } from 'redux-bundler';
|
|
|
2
2
|
import overlay from '../fieldCatalog.overlay.json';
|
|
3
3
|
import { study_info as VEP_STUDY_INFO } from '../vepStudyInfo';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// Fields matched by these globs are multi-valued in Solr.
|
|
6
|
+
const MULTIVALUED_PATTERNS = [
|
|
7
|
+
/__expr$/,
|
|
8
|
+
/__ancestors$/,
|
|
9
|
+
/__xrefs$/,
|
|
10
|
+
/^VEP__/,
|
|
11
|
+
/^homology__/
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function parseCsvHeader(text) {
|
|
15
|
+
if (!text) return [];
|
|
16
|
+
const line = text.split(/\r?\n/, 1)[0] || '';
|
|
17
|
+
return line
|
|
18
|
+
.split(',')
|
|
19
|
+
.map(s => s.trim().replace(/^"|"$/g, ''))
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function synthesizedValue(name) {
|
|
24
|
+
return MULTIVALUED_PATTERNS.some(re => re.test(name)) ? [0] : 0;
|
|
25
|
+
}
|
|
9
26
|
|
|
10
27
|
function applyTemplate(tpl, match) {
|
|
11
28
|
if (!tpl) return null;
|
|
@@ -66,43 +83,14 @@ function inferType(value) {
|
|
|
66
83
|
return typeof value;
|
|
67
84
|
}
|
|
68
85
|
|
|
69
|
-
function
|
|
70
|
-
const defs = (swagger && swagger.definitions) || {};
|
|
71
|
-
const out = {};
|
|
72
|
-
const collect = (schemaName, prefix = '') => {
|
|
73
|
-
const def = defs[schemaName];
|
|
74
|
-
if (!def || !def.properties) return;
|
|
75
|
-
for (const [prop, spec] of Object.entries(def.properties)) {
|
|
76
|
-
const fullName = prefix ? `${prefix}.${prop}` : prop;
|
|
77
|
-
let type = spec.type || 'object';
|
|
78
|
-
if (spec.$ref) type = 'ref:' + spec.$ref.split('/').pop();
|
|
79
|
-
if (spec.type === 'array') {
|
|
80
|
-
const inner = spec.items || {};
|
|
81
|
-
type = 'array<' + (inner.type || (inner.$ref ? inner.$ref.split('/').pop() : 'unknown')) + '>';
|
|
82
|
-
}
|
|
83
|
-
out[fullName] = {
|
|
84
|
-
name: fullName,
|
|
85
|
-
type,
|
|
86
|
-
description: spec.description || '',
|
|
87
|
-
source: 'swagger'
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
collect('Result');
|
|
92
|
-
collect('GeneDocument');
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function buildCatalog(swagger, sampleDocs) {
|
|
86
|
+
function buildCatalog(sampleDocs) {
|
|
97
87
|
const hidden = new Set(overlay.hidden || []);
|
|
98
88
|
const overlayFields = overlay.fields || {};
|
|
99
89
|
const patterns = overlay.patterns || [];
|
|
100
90
|
const groupsMeta = overlay.groups || {};
|
|
101
91
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// Union keys across sample docs + swagger keys
|
|
105
|
-
const keys = new Set(Object.keys(swaggerFields));
|
|
92
|
+
// Union keys from overlay.fields (hardcoded catalog entries) and sample docs.
|
|
93
|
+
const keys = new Set(Object.keys(overlayFields));
|
|
106
94
|
const sampleValues = {};
|
|
107
95
|
for (const doc of (sampleDocs || [])) {
|
|
108
96
|
for (const [k, v] of Object.entries(doc)) {
|
|
@@ -120,11 +108,10 @@ function buildCatalog(swagger, sampleDocs) {
|
|
|
120
108
|
if (classified && classified.isHidden) continue;
|
|
121
109
|
const sampleVal = sampleValues[name];
|
|
122
110
|
const sampleType = sampleVal !== undefined ? inferType(sampleVal) : null;
|
|
123
|
-
const swaggerEntry = swaggerFields[name];
|
|
124
111
|
|
|
125
112
|
const group = (overlayEntry && overlayEntry.group)
|
|
126
113
|
|| (classified && classified.group)
|
|
127
|
-
||
|
|
114
|
+
|| 'other';
|
|
128
115
|
|
|
129
116
|
let label = (overlayEntry && overlayEntry.label)
|
|
130
117
|
|| (classified && classified.label)
|
|
@@ -149,13 +136,13 @@ function buildCatalog(swagger, sampleDocs) {
|
|
|
149
136
|
tsvHeader,
|
|
150
137
|
group,
|
|
151
138
|
order: overlayEntry && overlayEntry.order != null ? overlayEntry.order : null,
|
|
152
|
-
description: (overlayEntry && overlayEntry.description) ||
|
|
153
|
-
type: sampleType ||
|
|
139
|
+
description: (overlayEntry && overlayEntry.description) || '',
|
|
140
|
+
type: sampleType || 'unknown',
|
|
154
141
|
multiValued,
|
|
155
142
|
patternId: classified ? classified.patternId : null,
|
|
156
143
|
matchGroups: classified ? classified.matchGroups : null,
|
|
157
144
|
expression: !!(classified && classified.expression),
|
|
158
|
-
source: overlayEntry ? 'overlay' :
|
|
145
|
+
source: overlayEntry ? 'overlay' : 'sample'
|
|
159
146
|
};
|
|
160
147
|
}
|
|
161
148
|
|
|
@@ -181,7 +168,6 @@ function buildCatalog(swagger, sampleDocs) {
|
|
|
181
168
|
|
|
182
169
|
return {
|
|
183
170
|
fetchedAt: Date.now(),
|
|
184
|
-
swaggerInfo: swagger && swagger.info ? swagger.info : null,
|
|
185
171
|
groups,
|
|
186
172
|
fields
|
|
187
173
|
};
|
|
@@ -194,59 +180,14 @@ const grameneFieldCatalog = createAsyncResourceBundle({
|
|
|
194
180
|
staleAfter: 5 * 60 * 1000,
|
|
195
181
|
getPromise: ({ store }) => {
|
|
196
182
|
const api = store.selectGrameneAPI();
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const mapsUrl = `${api}/maps?rows=-1`;
|
|
203
|
-
return Promise.all([
|
|
204
|
-
fetch(swaggerUrl).then(r => r.json()).catch(() => null),
|
|
205
|
-
fetch(sampleUrl).then(r => r.json()).catch(() => null),
|
|
206
|
-
fetch(experimentsUrl).then(r => r.json()).catch(() => []),
|
|
207
|
-
fetch(mapsUrl).then(r => r.json()).catch(() => [])
|
|
208
|
-
]).then(([swagger, sample, experiments, maps]) => {
|
|
209
|
-
const docs = (sample && sample.response && sample.response.docs) || [];
|
|
210
|
-
// Determine which species (anchor taxa) have differential experiments,
|
|
211
|
-
// then for each run a narrow discovery query scoped to that species'
|
|
212
|
-
// strain taxa so we actually surface their diffexpr field names.
|
|
213
|
-
const diffTaxa = [...new Set(
|
|
214
|
-
(experiments || [])
|
|
215
|
-
.filter(e => e && e.type === 'Differential' && e.taxon_id)
|
|
216
|
-
.map(e => e.taxon_id)
|
|
217
|
-
)];
|
|
218
|
-
const strainsByAnchor = {};
|
|
219
|
-
for (const m of (maps || [])) {
|
|
220
|
-
const anchor = m && m.anchor_taxon_id;
|
|
221
|
-
if (!anchor) continue;
|
|
222
|
-
(strainsByAnchor[anchor] = strainsByAnchor[anchor] || []).push(m.taxon_id);
|
|
223
|
-
}
|
|
224
|
-
const discoveryFetches = diffTaxa.map(t => {
|
|
225
|
-
const strains = strainsByAnchor[t] || [t];
|
|
226
|
-
const fq = `taxon_id:(${strains.join(' OR ')})`;
|
|
227
|
-
const url = `${api}/search?q=${encodeURIComponent(SAMPLE_QUERY)}`
|
|
228
|
-
+ `&rows=${DIFFEXPR_ROWS_PER_TAXON}`
|
|
229
|
-
+ `&fl=${encodeURIComponent(DIFFEXPR_FL)}`
|
|
230
|
-
+ `&fq=${encodeURIComponent(fq)}`;
|
|
231
|
-
return fetch(url).then(r => r.json()).catch(() => null);
|
|
232
|
-
});
|
|
233
|
-
return Promise.all(discoveryFetches).then(results => {
|
|
234
|
-
const diffDocs = results.flatMap(r => (r && r.response && r.response.docs) || []);
|
|
235
|
-
return buildCatalog(swagger, [...docs, ...diffDocs]);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
183
|
+
const url = `${api}/search?q=*:*&rows=0&wt=csv&fl=*`;
|
|
184
|
+
return fetch(url)
|
|
185
|
+
.then(r => r.text())
|
|
186
|
+
.then(text => parseCsvHeader(text))
|
|
187
|
+
.catch(() => null);
|
|
238
188
|
}
|
|
239
189
|
});
|
|
240
190
|
|
|
241
|
-
grameneFieldCatalog.reactGrameneFieldCatalog = createSelector(
|
|
242
|
-
'selectGrameneFieldCatalogShouldUpdate',
|
|
243
|
-
(shouldUpdate) => {
|
|
244
|
-
if (shouldUpdate) {
|
|
245
|
-
return { actionCreator: 'doFetchGrameneFieldCatalog' };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
);
|
|
249
|
-
|
|
250
191
|
function assayFactorLabel(assay) {
|
|
251
192
|
if (!assay) return '';
|
|
252
193
|
const factors = Array.isArray(assay.factor) ? assay.factor : [];
|
|
@@ -592,13 +533,51 @@ function enrichLabels(catalog, expressionStudies, expressionSamples, grameneSear
|
|
|
592
533
|
return { ...catalog, fields: fieldsOut, groups };
|
|
593
534
|
}
|
|
594
535
|
|
|
536
|
+
function filterCatalogByPresentFields(catalog, present) {
|
|
537
|
+
if (!catalog || !catalog.fields || !present) return catalog;
|
|
538
|
+
const filteredFields = {};
|
|
539
|
+
for (const [name, f] of Object.entries(catalog.fields)) {
|
|
540
|
+
if (present.has(name)) filteredFields[name] = f;
|
|
541
|
+
}
|
|
542
|
+
const hasContent = (g) =>
|
|
543
|
+
(g.fields && g.fields.length > 0) ||
|
|
544
|
+
(g.subgroups && g.subgroups.some(hasContent));
|
|
545
|
+
const filterGroup = (g) => {
|
|
546
|
+
const fields = (g.fields || []).filter(n => present.has(n));
|
|
547
|
+
const subgroups = (g.subgroups || [])
|
|
548
|
+
.map(filterGroup)
|
|
549
|
+
.filter(hasContent);
|
|
550
|
+
return { ...g, fields, subgroups };
|
|
551
|
+
};
|
|
552
|
+
const groups = (catalog.groups || []).map(filterGroup).filter(hasContent);
|
|
553
|
+
return { ...catalog, fields: filteredFields, groups };
|
|
554
|
+
}
|
|
555
|
+
|
|
595
556
|
grameneFieldCatalog.selectFieldCatalog = createSelector(
|
|
596
557
|
'selectGrameneFieldCatalog',
|
|
597
558
|
'selectExpressionStudies',
|
|
598
559
|
'selectExpressionSamples',
|
|
599
560
|
'selectGrameneSearch',
|
|
600
561
|
'selectGrameneMaps',
|
|
601
|
-
|
|
562
|
+
(fieldNames, studies, samples, search, maps) => {
|
|
563
|
+
if (!fieldNames || !fieldNames.length) return null;
|
|
564
|
+
const present = new Set(fieldNames);
|
|
565
|
+
// Build a synthetic doc from the discovered field names; values carry the
|
|
566
|
+
// right JS type so inferType picks the correct multiValued/type (arrays
|
|
567
|
+
// for multi-valued fields, scalars otherwise). Derive pval/logfc
|
|
568
|
+
// companions from every l2fc field.
|
|
569
|
+
const doc = {};
|
|
570
|
+
for (const name of present) {
|
|
571
|
+
doc[name] = synthesizedValue(name);
|
|
572
|
+
if (/_l2fc_attr_f$/.test(name)) {
|
|
573
|
+
doc[name.replace('_l2fc_', '_pval_')] = 0;
|
|
574
|
+
doc[name.replace('_l2fc_', '_logfc_')] = 0;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const catalog = buildCatalog([doc]);
|
|
578
|
+
const enriched = enrichLabels(catalog, studies, samples, search, maps);
|
|
579
|
+
return filterCatalogByPresentFields(enriched, present);
|
|
580
|
+
}
|
|
602
581
|
);
|
|
603
582
|
|
|
604
583
|
grameneFieldCatalog.selectFieldCatalogGroups = createSelector(
|
package/src/bundles/views.js
CHANGED
|
@@ -115,7 +115,7 @@ const grameneViews = {
|
|
|
115
115
|
const touched = raw.touched || {};
|
|
116
116
|
const numFound = (search && search.response && search.response.numFound) || 0;
|
|
117
117
|
const hasFilters = !!(filters && filters.rightIdx > 1);
|
|
118
|
-
const resultDependentIds = new Set(['taxonomy', 'list']);
|
|
118
|
+
const resultDependentIds = new Set(['taxonomy', 'list', 'export']);
|
|
119
119
|
const autoDisable = (numFound === 0) || !hasFilters;
|
|
120
120
|
const hasFirebase = !!(config && config.firebaseConfig);
|
|
121
121
|
|
|
@@ -127,8 +127,8 @@ const grameneViews = {
|
|
|
127
127
|
options = options.filter(v => overrides[v.id] !== 'hidden');
|
|
128
128
|
}
|
|
129
129
|
options = options.map(v => {
|
|
130
|
-
if (
|
|
131
|
-
return { ...v, show: '
|
|
130
|
+
if (resultDependentIds.has(v.id) && autoDisable) {
|
|
131
|
+
return { ...v, show: 'off' };
|
|
132
132
|
}
|
|
133
133
|
if (!overrides) return v;
|
|
134
134
|
const o = overrides[v.id];
|
|
@@ -137,6 +137,10 @@ const grameneViews = {
|
|
|
137
137
|
if (touched[v.id]) return v;
|
|
138
138
|
return { ...v, show: o };
|
|
139
139
|
});
|
|
140
|
+
const anyOtherOn = options.some(v => v.id !== 'help' && v.show === 'on');
|
|
141
|
+
if (!anyOtherOn) {
|
|
142
|
+
options = options.map(v => v.id === 'help' ? { ...v, show: 'on' } : v);
|
|
143
|
+
}
|
|
140
144
|
return { ...raw, options };
|
|
141
145
|
}
|
|
142
146
|
)
|
|
@@ -12,13 +12,19 @@ const ExporterViewCmp = props => {
|
|
|
12
12
|
const {
|
|
13
13
|
fieldCatalog: catalog,
|
|
14
14
|
grameneFieldCatalogIsLoading: isLoading,
|
|
15
|
-
grameneFieldCatalogRaw: raw
|
|
15
|
+
grameneFieldCatalogRaw: raw,
|
|
16
|
+
grameneFieldCatalogShouldUpdate: shouldUpdate,
|
|
17
|
+
doFetchGrameneFieldCatalog
|
|
16
18
|
} = props;
|
|
17
19
|
const [fieldQuery, setFieldQuery] = useState('');
|
|
18
20
|
const [leftPct, setLeftPct] = useState(50);
|
|
19
21
|
const [dragging, setDragging] = useState(false);
|
|
20
22
|
const containerRef = useRef(null);
|
|
21
23
|
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (shouldUpdate) doFetchGrameneFieldCatalog();
|
|
26
|
+
}, [shouldUpdate, doFetchGrameneFieldCatalog]);
|
|
27
|
+
|
|
22
28
|
const onPointerMove = useCallback((e) => {
|
|
23
29
|
const el = containerRef.current;
|
|
24
30
|
if (!el) return;
|
|
@@ -103,5 +109,7 @@ export default connect(
|
|
|
103
109
|
'selectFieldCatalog',
|
|
104
110
|
'selectGrameneFieldCatalogIsLoading',
|
|
105
111
|
'selectGrameneFieldCatalogRaw',
|
|
112
|
+
'selectGrameneFieldCatalogShouldUpdate',
|
|
113
|
+
'doFetchGrameneFieldCatalog',
|
|
106
114
|
ExporterViewCmp
|
|
107
115
|
);
|
|
@@ -170,5 +170,6 @@
|
|
|
170
170
|
"compara_idx", "compara_idx_multi",
|
|
171
171
|
"_id", "annotations", "bins", "gene_structure",
|
|
172
172
|
"homology", "location", "xrefs",
|
|
173
|
-
"domain_roots", "familyRoot__ancestors", "taxonomy__ancestors", "capabilities"
|
|
173
|
+
"domain_roots", "familyRoot__ancestors", "taxonomy__ancestors", "capabilities",
|
|
174
|
+
"saved_search"
|
|
174
175
|
]}
|