ucsc-xena-client
Version:
UCSC Xena Client. Functional genomics visualizations.
365 lines (307 loc) • 12.4 kB
JavaScript
;
// Helper methods needed by multiple controllers.
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var Rx = require('../rx');
var xenaQuery = require('../xenaQuery');
var _ = require('../underscore_ext');
var _require = require('./errors'),
reifyErrors = _require.reifyErrors,
collectResults = _require.collectResults;
var fetch = require('../fieldFetch');
var kmModel = require('../models/km');
var _require2 = require('../models/datasetJoins'),
getColSpec = _require2.getColSpec;
var _require3 = require('../models/fieldSpec'),
signatureField = _require3.signatureField;
var defaultServers = require('../defaultServers');
var publicServers = defaultServers.publicServers;
var gaEvents = require('../gaEvents');
// pick up signature fetch
require('../models/signatures');
var datasetResults = function datasetResults(resps) {
return collectResults(resps, function (servers) {
return _.object(_.flatmap(servers, function (s) {
return _.map(s.datasets, function (d) {
return [d.dsID, d];
});
}));
});
};
function datasetQuery(servers, cohort) {
return Rx.Observable.zipArray(_.map(servers, function (server) {
return reifyErrors(xenaQuery.datasetList(server, [cohort.name]).map(function (datasets) {
return { server: server, datasets: datasets };
}), { host: server });
})).flatMap(datasetResults);
}
function fetchDatasets(serverBus, servers, cohort) {
serverBus.next(['datasets', datasetQuery(servers, cohort)]);
}
var MAX_SAMPLES = 50 * 1000;
var allSamples = _.curry(function (cohort, max, server) {
return xenaQuery.cohortSamples(server, cohort, max === Infinity ? null : max);
});
function unionOfGroup(gb) {
return _.union.apply(_, _toConsumableArray(_.map(gb, function (_ref) {
var _ref2 = _slicedToArray(_ref, 1),
v = _ref2[0];
return v;
})));
}
function logSampleSources(cohortResps) {
var havingSamples = cohortResps.filter(function (_ref3) {
var _ref4 = _slicedToArray(_ref3, 1),
v = _ref4[0];
return v.length > 0;
}).map(function (_ref5) {
var _ref6 = _slicedToArray(_ref5, 3),
server = _ref6[2];
return server;
}),
types = new Set(havingSamples.map(function (s) {
return s === defaultServers.servers.localHub ? 'localhost' :
// counting all ucsc-hosted hubs as public
s.indexOf('.xenahubs.net') !== -1 ? 'public' : 'private';
}));
if (types.has('localhost')) {
// user has samples on localhost hub
gaEvents('hubs', 'localhost');
}
if (types.has('private')) {
// user has samples on private hub that isn't localhost
gaEvents('hubs', 'private');
}
if (types.size > 1 && (types.has('localhost') || types.has('private'))) {
// user is joining samples across hubs, and not all of them
// are xena public hubs.
gaEvents('hubs', 'cohort join');
}
}
// Performance of this is probably poor, esp. due to underscore's horrible
// n^2 set operations.
function cohortHasPrivateSamples(cohortResps) {
var _$groupBy = _.groupBy(cohortResps, function (_ref7) {
var _ref8 = _slicedToArray(_ref7, 3),
server = _ref8[2];
return _.contains(publicServers, server);
}),
pub = _$groupBy['true'],
priv = _$groupBy['false'],
pubSamps = unionOfGroup(pub),
privSamps = unionOfGroup(priv);
return _.difference(privSamps, pubSamps).length > 0;
}
function filterSamples(sampleFilter, samples) {
return sampleFilter ? _.intersection(sampleFilter, samples) : samples;
}
// For the cohort, query all servers,
// return a stream per-cohort, each of which returns an event
// [cohort, [sample, ...]].
// By not combining them here, we can uniformly handle errors, below.
var cohortSamplesQuery = function cohortSamplesQuery(servers, max, _ref9) {
var name = _ref9.name,
sampleFilter = _ref9.sampleFilter;
return _.map(servers, allSamples(name, max)).map(function (resp, j) {
return resp.map(function (samples) {
return [filterSamples(sampleFilter, samples), samples.length >= max, servers[j]];
});
});
};
var collateSamples = _.curry(function (cohorts, max, resps) {
var serverOver = _.any(resps, function (_ref10) {
var _ref11 = _slicedToArray(_ref10, 2),
over = _ref11[1];
return over;
}),
cohortSamples = unionOfGroup(resps || []).slice(0, max),
cohortOver = cohortSamples.length >= max,
hasPrivateSamples = cohortHasPrivateSamples(resps);
logSampleSources(resps);
return { samples: cohortSamples, over: serverOver || cohortOver, hasPrivateSamples: hasPrivateSamples };
});
// reifyErrors should be pass the server name, but in this expression we don't have it.
function samplesQuery(servers, cohort, max) {
return Rx.Observable.zipArray(cohortSamplesQuery(servers, max, cohort).map(reifyErrors)).flatMap(function (resps) {
return collectResults(resps, collateSamples(cohort, max));
});
}
function fetchSamples(serverBus, servers, cohort, allowOverSamples) {
serverBus.next(['samples', samplesQuery(servers, cohort, allowOverSamples ? Infinity : MAX_SAMPLES)]);
}
function fetchColumnData(serverBus, samples, id, settings) {
// XXX Note that the widget-data-xxx slots are leaked in the groupBy
// in main.js. We need a better mechanism.
// if (Math.random() > 0.5) { // testing error handling
serverBus.next([['widget-data', id], fetch(settings, samples)]);
// } else {
// serverBus.onNext([['widget-data', id], Rx.Observable.throw(new Error('Injected error'))]);
// }
}
function resetZoom(state) {
var count = _.getIn(state, ['cohortSamples', 'length'], 0);
return _.updateIn(state, ["zoom"], function (z) {
return _.merge(z, { count: count, index: 0 });
});
}
var setCohortRelatedFields = function setCohortRelatedFields(state, cohort) {
return _.assoc(state, 'cohort', cohort, 'hasPrivateSamples', false, 'cohortSamples', [], 'columns', {}, 'columnOrder', [], 'data', {}, 'survival', null, 'km', _.assoc(state.km, ['id'], null));
};
// This adds or overwrites a 'sample' column in the state.
// Called from setCohort, the column data will be fetched after
// the sample list returns from the server.
function addSampleColumn(state, width) {
if (!_.get(state.cohort, ['name'])) {
return state;
}
var field = signatureField('samples', {
columnLabel: 'Sample ID',
valueType: 'coded',
signature: ['samples']
}),
newOrder = _.has(state.columns, 'samples') ? state.columnOrder : [].concat(_toConsumableArray(state.columnOrder), ['samples']),
colSpec = getColSpec([field], {}),
settings = _.assoc(colSpec, 'width', Math.round(width == null ? 136 : width), 'user', _.pick(colSpec, ['columnLabel', 'fieldLabel'])),
newState = _.assocIn(state, ['columns', 'samples'], settings, ['columnOrder'], newOrder);
return _.assocIn(newState, ['data', 'samples', 'status'], 'loading');
}
var setWizardAndMode = function setWizardAndMode(state) {
return _.assocIn(state, ['wizardMode'], true, ['mode'], 'heatmap');
};
var setCohort = _.curry(function (cohort, width, state) {
return addSampleColumn(setWizardAndMode(resetZoom(setCohortRelatedFields(state, cohort))), width);
});
var userServers = function userServers(state) {
return _.keys(state.servers).filter(function (h) {
return state.servers[h].user;
});
};
var fetchCohortData = function fetchCohortData(serverBus, state) {
var user = userServers(state);
if (state.cohort) {
fetchDatasets(serverBus, user, state.cohort);
fetchSamples(serverBus, user, state.cohort, state.allowOverSamples);
}
};
var unionOfResults = function unionOfResults(resps) {
return collectResults(resps, function (results) {
return _.union.apply(_, _toConsumableArray(results));
});
};
function cohortQuery(servers) {
return Rx.Observable.zipArray(_.map(servers, function (s) {
return reifyErrors(xenaQuery.allCohorts(s), { host: s });
})).flatMap(unionOfResults);
}
function fetchCohorts(serverBus, state, newState) {
var _ref12 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
force = _ref12.force;
var user = userServers(state),
newUser = userServers(newState);
if (force || !_.listSetsEqual(user, newUser)) {
serverBus.next(['cohorts', cohortQuery(newUser)]);
}
}
function updateWizard(serverBus, state, newState) {
var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
fetchCohorts(serverBus, state, newState, opts);
var user = userServers(newState);
// If there's a bookmark on wizard mode step 2, will we fail
// to load the dataset?
if (newState.cohort && (opts.force || newState.cohort.name !== _.get(state.cohort, 'name'))) {
fetchDatasets(serverBus, user, newState.cohort);
}
}
var clearWizardCohort = function clearWizardCohort(state) {
return _.assocIn(state, ['wizard', 'datasets'], undefined, ['wizard', 'features'], undefined);
};
//
// survival fields
//
var hasSurvFields = function hasSurvFields(vars) {
return !!_.some(_.values(kmModel.survivalOptions), function (option) {
return vars[option.ev] && vars[option.tte] && vars[option.patient];
});
};
var probeFieldSpec = function probeFieldSpec(_ref13) {
var dsID = _ref13.dsID,
name = _ref13.name;
return {
dsID: dsID,
fetchType: 'xena', // maybe take from dataset meta instead of hard-coded
valueType: 'float',
fieldType: 'probes',
fields: [name]
};
};
var codedFieldSpec = function codedFieldSpec(_ref14) {
var dsID = _ref14.dsID,
name = _ref14.name;
return {
dsID: dsID,
fetchType: 'xena', // maybe take from dataset meta instead of hard-coded
valueType: 'coded',
fieldType: 'clinical',
fields: [name]
};
};
function mapToObj(keys, fn) {
return _.object(keys, _.map(keys, fn));
}
function survivalFields(cohort, datasets, features) {
var vars = kmModel.pickSurvivalVars(features),
fields = {};
if (hasSurvFields(vars)) {
fields['patient'] = getColSpec([codedFieldSpec(vars.patient)], datasets);
_.values(kmModel.survivalOptions).forEach(function (option) {
if (vars[option.ev] && vars[option.tte]) {
fields[option.ev] = getColSpec([probeFieldSpec(vars[option.ev])], datasets);
fields[option.tte] = getColSpec([probeFieldSpec(vars[option.tte])], datasets);
}
});
if (_.has(fields, 'ev') && _.keys(fields).length > 3) {
delete fields.ev;
delete fields.tte;
}
}
return fields;
}
// If field set has changed, re-fetch.
function fetchSurvival(serverBus, state) {
var _Rx$Observable;
var _state$wizard = state.wizard,
datasets = _state$wizard.datasets,
features = _state$wizard.features,
_state$spreadsheet = state.spreadsheet,
cohort = _state$spreadsheet.cohort,
survival = _state$spreadsheet.survival,
cohortSamples = _state$spreadsheet.cohortSamples,
fields = survivalFields(cohort, datasets, features),
survFields = _.keys(fields),
refetch = _.some(survFields, function (f) {
return !_.isEqual(fields[f], _.getIn(survival, [f, 'field']));
}),
queries = _.map(survFields, function (key) {
return fetch(fields[key], cohortSamples);
}),
collate = function collate(data) {
return mapToObj(survFields, function (k, i) {
return { field: fields[k], data: data[i] };
});
};
refetch && serverBus.next(['km-survival-data', (_Rx$Observable = Rx.Observable).zipArray.apply(_Rx$Observable, _toConsumableArray(queries)).map(collate)]);
}
module.exports = {
fetchCohortData: fetchCohortData,
fetchCohorts: fetchCohorts,
fetchColumnData: fetchColumnData,
fetchDatasets: fetchDatasets,
fetchSamples: fetchSamples,
fetchSurvival: fetchSurvival,
resetZoom: resetZoom,
setCohort: setCohort,
userServers: userServers,
updateWizard: updateWizard,
clearWizardCohort: clearWizardCohort,
datasetQuery: datasetQuery
};