[^)<]+)'
@@ -27,7 +27,7 @@ def _databases_support_transactions(cls):
@classmethod
def _rollback_atomics(cls, atomics):
- """Django testcases asssume either all database support transactions or none do. This properly cleans up transaction blocks on a per-db basis"""
+ # Django testcases asssume either all database support transactions or none do. This properly cleans up transaction blocks on a per-db basis
for db_name in reversed(cls._databases_names()):
if connections[db_name].features.supports_transactions:
transaction.set_rollback(True, using=db_name)
@@ -170,15 +170,6 @@ def login_data_manager_user(self):
def _check_login(self, url, permission_level, request_data=None, login_redirect_url='/api/login-required-error',
policy_redirect_url='/api/policy-required-error', permission_denied_error=403):
- """For integration tests of django views that can only be accessed by a logged-in user,
- the 1st step is to authenticate. This function checks that the given url redirects requests
- if the user isn't logged-in, and then authenticates a test user.
-
- Args:
- test_case (object): the django.TestCase or unittest.TestCase object
- url (string): The url of the django view being tested.
- permission_level (string): what level of permission this url requires
- """
# check that it redirects if you don't login
parsed_url = urlparse(url)
next_query = quote_plus('?{}'.format(parsed_url.query)) if parsed_url.query else ''
@@ -703,9 +694,8 @@ def _get_list_param(call, param):
INTERNAL_INDIVIDUAL_FIELDS = deepcopy(NO_INTERNAL_CASE_REVIEW_INDIVIDUAL_FIELDS)
INTERNAL_INDIVIDUAL_FIELDS.update(CORE_INTERNAL_INDIVIDUAL_FIELDS)
-SAMPLE_FIELDS = {
- 'projectGuid', 'familyGuid', 'individualGuid', 'sampleGuid', 'createdDate', 'sampleType', 'sampleId', 'isActive',
- 'loadedDate', 'datasetType',
+DATASET_FIELDS = {
+ 'projectGuid', 'datasetGuid', 'sampleType', 'loadedDate', 'datasetType', 'activeIndividuals', 'inactiveIndividuals',
}
IGV_SAMPLE_FIELDS = {
@@ -722,7 +712,7 @@ def _get_list_param(call, param):
SAVED_VARIANT_DETAIL_FIELDS.update(SAVED_VARIANT_FIELDS)
TAG_FIELDS = {
- 'tagGuid', 'name', 'category', 'color', 'searchHash', 'metadata', 'lastModifiedDate', 'createdBy', 'variantGuids',
+ 'tagGuid', 'name', 'category', 'color', 'searchHash', 'searchName', 'metadata', 'lastModifiedDate', 'createdBy', 'variantGuids',
}
VARIANT_NOTE_FIELDS = {'noteGuid', 'note', 'report', 'lastModifiedDate', 'createdBy', 'variantGuids'}
diff --git a/seqr/views/utils/variant_utils.py b/seqr/views/utils/variant_utils.py
index eab4d543b2..19088364e6 100644
--- a/seqr/views/utils/variant_utils.py
+++ b/seqr/views/utils/variant_utils.py
@@ -12,9 +12,9 @@
from matchmaker.models import MatchmakerSubmissionGenes, MatchmakerSubmission
from reference_data.models import TranscriptInfo, Omim, GENOME_VERSION_GRCh38
from seqr.models import SavedVariant, VariantSearchResults, Family, LocusList, LocusListInterval, LocusListGene, \
- RnaSeqTpm, PhenotypePrioritization, Project, Sample, RnaSample, VariantTag, VariantTagType
-from seqr.utils.search.utils import variant_dataset_type
+ RnaSeqTpm, PhenotypePrioritization, Project, Dataset, RnaSample, VariantTag, VariantTagType
from seqr.utils.gene_utils import get_genes_for_variants
+from seqr.utils.xpos_utils import parse_variant_id
from seqr.views.utils.json_to_orm_utils import create_model_from_json
from seqr.views.utils.orm_to_json_utils import get_json_for_discovery_tags, get_json_for_locus_lists, \
get_json_for_queryset, get_json_for_rna_seq_outliers, get_json_for_saved_variants_with_tags, \
@@ -63,6 +63,13 @@ def parse_saved_variant_json(variant_json, family_id):
}
+def variant_dataset_type(variant):
+ if not parse_variant_id(variant['variantId']):
+ sample_type = Dataset.SAMPLE_TYPE_WGS if 'endChrom' in variant else Dataset.SAMPLE_TYPE_WES
+ return f'{Dataset.DATASET_TYPE_SV_CALLS}_{sample_type}'
+ return Dataset.DATASET_TYPE_MITO_CALLS if 'mitomapPathogenic' in variant else Dataset.DATASET_TYPE_VARIANT_CALLS
+
+
def _transcript_sort(gene_id, saved_variant_json, main_transcript_id):
gene_transcripts = saved_variant_json['transcripts'][gene_id]
is_main_gene = bool(main_transcript_id) and any(t.get('transcriptId') == main_transcript_id for t in gene_transcripts)
@@ -171,7 +178,7 @@ def _set_updated_tags(key: tuple[int, str], metadata: dict[str, dict], comp_het_
def get_saved_variant_annotations(variant_keys: abc.Iterable[tuple[int, str]], genome_version: str, primary_id_field: str = 'variant_id', group_by_field=None, dataset_type: str = None) -> dict[str, dict]:
- dataset_type = dataset_type or Sample.DATASET_TYPE_VARIANT_CALLS
+ dataset_type = dataset_type or Dataset.DATASET_TYPE_VARIANT_CALLS
variant_ids = {variant_id for _, variant_id in variant_keys}
keys = None
if primary_id_field == 'key':
@@ -490,9 +497,9 @@ def _set_response_gene_scores(response, family_genes, gene_ids):
def _add_sample_count_stats(response, genome_versions):
- sample_counts = Sample.objects.filter(
- is_active=True, individual__family__project__is_demo=False, individual__family__project__genome_version__in=genome_versions,
- ).values('sample_type', 'dataset_type').annotate(count=Count('*'))
+ sample_counts = Dataset.objects.filter(
+ active_individuals__family__project__is_demo=False, active_individuals__family__project__genome_version__in=genome_versions,
+ ).values('sample_type', 'dataset_type').annotate(count=Count('active_individuals', distinct=True))
counts_by_dataset_type = defaultdict(dict)
for sample_type, dataset_type, count in sample_counts.values_list('sample_type', 'dataset_type', 'count'):
counts_by_dataset_type[dataset_type][sample_type] = count
diff --git a/settings.py b/settings.py
index 94fc51a8b3..5c3871d8cf 100644
--- a/settings.py
+++ b/settings.py
@@ -4,8 +4,6 @@
import string
import subprocess # nosec
-from ssl import create_default_context
-
import google.auth
import google.auth.transport.requests
diff --git a/ui/pages/Project/components/EditDatasetsButton.jsx b/ui/pages/Project/components/EditDatasetsButton.jsx
index 90e5e2fb61..23b8791897 100644
--- a/ui/pages/Project/components/EditDatasetsButton.jsx
+++ b/ui/pages/Project/components/EditDatasetsButton.jsx
@@ -11,7 +11,7 @@ import { UPLOAD_PROJECT_IGV_FIELD } from 'shared/components/form/IGVUploadField'
import FileUploadField from 'shared/components/form/XHRUploaderField'
import { Select } from 'shared/components/form/Inputs'
import AddWorkspaceDataForm from 'shared/components/panel/LoadWorkspaceDataForm'
-import { LOAD_RNA_FIELDS, TISSUE_DISPLAY } from 'shared/utils/constants'
+import { LOAD_RNA_FIELDS, TISSUE_DISPLAY, PRODUCT_DISPLAY } from 'shared/utils/constants'
import { addIGVDataset, uploadRnaSeq } from '../reducers'
import { getCurrentProject, getProjectGuid, getRnaSeqUploadStats } from '../selectors'
@@ -72,6 +72,13 @@ const PROJECT_LOAD_RNA_FIELDS = [
options: Object.entries(TISSUE_DISPLAY).map(([value, name]) => ({ value, name })),
validate: validators.required,
},
+ {
+ name: 'sequencingType',
+ label: 'Sequencing Type',
+ component: Select,
+ options: Object.entries(PRODUCT_DISPLAY).map(([value, name]) => ({ value, name })),
+ validate: validators.required,
+ },
...LOAD_RNA_FIELDS.slice(-2),
]
diff --git a/ui/pages/Project/components/ProjectOverview.jsx b/ui/pages/Project/components/ProjectOverview.jsx
index 82ef203dad..be47568655 100644
--- a/ui/pages/Project/components/ProjectOverview.jsx
+++ b/ui/pages/Project/components/ProjectOverview.jsx
@@ -29,6 +29,7 @@ import {
getProjectAnalysisGroupFamilySizeHistogram,
getProjectAnalysisGroupDataLoadedFamilySizeHistogram,
getProjectAnalysisGroupSamplesByTypes,
+ getProjectAnalysisGroupRnaSamplesByTypes,
getProjectAnalysisGroupMmeSubmissionDetails,
getMmeSubmissionsLoading,
} from '../selectors'
@@ -303,15 +304,19 @@ class DatasetSection extends React.PureComponent {
}
-const Dataset = React.memo(({ showLoadWorkspaceData, hasAnvil, samplesByType, user }) => {
- const datasetSections = samplesByType.map(([sampleTypeKey, loadedSampleCounts]) => {
+const Dataset = React.memo(({ showLoadWorkspaceData, hasAnvil, samplesByType, rnaSamplesByType, user }) => {
+ const datasetSections = [...Object.entries(samplesByType).map(([sampleTypeKey, loadedSampleCounts]) => {
const [sampleType, datasetType] = sampleTypeKey.split('__')
return {
key: sampleTypeKey,
title: `${SAMPLE_TYPE_LOOKUP[sampleType] || sampleType}${DATASET_TITLE_LOOKUP[datasetType] || ''} Datasets`,
content: ,
}
- }).sort((a, b) => a.title.localeCompare(b.title))
+ }), ...rnaSamplesByType.map(([dataType, loadedSampleCounts]) => ({
+ key: dataType,
+ title: `RNA${DATASET_TITLE_LOOKUP[dataType]} Datasets`,
+ content: ,
+ }))].sort((a, b) => a.title.localeCompare(b.title))
const noLoadedData = !datasetSections.length
if (noLoadedData) {
@@ -358,6 +363,7 @@ const Dataset = React.memo(({ showLoadWorkspaceData, hasAnvil, samplesByType, us
Dataset.propTypes = {
samplesByType: PropTypes.object.isRequired,
+ rnaSamplesByType: PropTypes.arrayOf(PropTypes.any).isRequired,
hasAnvil: PropTypes.bool,
showLoadWorkspaceData: PropTypes.bool,
user: PropTypes.object.isRequired,
@@ -366,6 +372,7 @@ Dataset.propTypes = {
const mapDatasetStateToProps = (state, ownProps) => ({
user: getUser(state),
samplesByType: getProjectAnalysisGroupSamplesByTypes(state, ownProps),
+ rnaSamplesByType: getProjectAnalysisGroupRnaSamplesByTypes(state, ownProps),
})
const DatasetOverview = connect(mapDatasetStateToProps)(Dataset)
diff --git a/ui/pages/Project/components/RnaSeqResultPage.jsx b/ui/pages/Project/components/RnaSeqResultPage.jsx
index 8477420bb2..0546c4916c 100644
--- a/ui/pages/Project/components/RnaSeqResultPage.jsx
+++ b/ui/pages/Project/components/RnaSeqResultPage.jsx
@@ -5,7 +5,7 @@ import { connect } from 'react-redux'
import { Loader, Grid, Dropdown } from 'semantic-ui-react'
import { getGenesById, getIndividualsByGuid, getRnaSeqSignificantJunctionData } from 'redux/selectors'
-import { RNASEQ_JUNCTION_PADDING, TISSUE_DISPLAY } from 'shared/utils/constants'
+import { RNASEQ_JUNCTION_PADDING } from 'shared/utils/constants'
import DataLoader from 'shared/components/DataLoader'
import FamilyReads from 'shared/components/panel/family/FamilyReads'
import RnaSeqJunctionOutliersTable from 'shared/components/table/RnaSeqJunctionOutliersTable'
@@ -46,44 +46,44 @@ class BaseRnaSeqResultPage extends React.PureComponent {
}
state = {
- tissueType: '',
+ selectedDataType: null,
}
onTissueChange = (e, data) => {
- this.setState({ tissueType: data.value })
+ this.setState({ selectedDataType: data.value })
}
render() {
const { familyGuid, rnaSeqData, significantJunctionOutliers, genesById, tissueOptions } = this.props
- const { tissueType } = this.state
- const defaultTissue = tissueOptions?.length > 0 ? tissueOptions[0].value : null
- const showTissueType = tissueType === '' ? defaultTissue : tissueType
- const tissueDisplay = TISSUE_DISPLAY[showTissueType]
+ const { selectedDataType } = this.state
+ const showDataType = selectedDataType || tissueOptions[0].value
+ const [showTissueType, showSequencingType] = showDataType.split('-')
const outlierPlotConfigs = OUTLIER_VOLCANO_PLOT_CONFIGS.map((config) => {
- const data = rnaSeqData[config.key]
- return ({ data: data.filter(outlier => outlier.tissueType === showTissueType), ...config })
+ const data = rnaSeqData[config.key].filter(
+ outlier => outlier.tissueType === showTissueType && outlier.sequencingType === showSequencingType,
+ )
+ return ({ data, ...config })
}).filter(({ data }) => data.length)
const tableData = significantJunctionOutliers.reduce(
- (acc, outlier) => (outlier.tissueType === showTissueType ? [...acc, outlier] : acc), [],
+ (acc, outlier) => (
+ (outlier.tissueType === showTissueType && outlier.sequencingType === showSequencingType) ?
+ [...acc, outlier] : acc
+ ), [],
)
return (
- {(showTissueType || tissueOptions?.length > 1) && (
-
- Tissue type:
- {tissueOptions?.length > 1 ? (
-
- ) : tissueDisplay}
-
- )}
+
+ {tissueOptions?.length > 1 ? (
+
+ ) : tissueOptions[0].text}
+
{ outlierPlotConfigs.length > 0 && (
}>
diff --git a/ui/pages/Project/fixtures.js b/ui/pages/Project/fixtures.js
index 033fc74b57..6689bcb1e0 100644
--- a/ui/pages/Project/fixtures.js
+++ b/ui/pages/Project/fixtures.js
@@ -121,7 +121,7 @@ export const STATE1 = {
sex: 'M',
},
},
- samplesByGuid: {},
+ datasetsByGuid: {},
mmeSubmissionsByGuid: {},
project: {
createdDate: '2016-05-16T05:37:08.634Z',
@@ -310,19 +310,14 @@ export const STATE_WITH_2_FAMILIES = {
sex: 'M',
},
},
- samplesByGuid: {
+ datasetsByGuid: {
S2310656_wal_mc16200_mc16203: {
- createdDate: "2018-03-30T11:50:40.079Z",
- datasetFilePath: "gs://seqr-datasets/GRCh37/cmg_sankaran_wes/CMG_MYOSEQ.vcf.gz",
- datasetName: null,
datasetType: "SNV_INDEL",
- familyGuid: 'F011652_2',
- individualGuid: "I021476_na19678_2",
+ activeIndividuals: ["I021476_na19678_2"],
+ inactiveIndividuals: [],
loadedDate: "2018-03-13T13:25:21.551Z",
projectGuid: "R0237_1000_genomes_demo",
- sampleGuid: "S2310656_wal_mc16200_mc16203",
- sampleId: "WAL_MC16200_MC16203",
- isActive: true,
+ datasetGuid: "S2310656_wal_mc16200_mc16203",
sampleType: "WES",
},
},
diff --git a/ui/pages/Project/reducers.js b/ui/pages/Project/reducers.js
index c2181b8dfe..9d0d355724 100644
--- a/ui/pages/Project/reducers.js
+++ b/ui/pages/Project/reducers.js
@@ -56,11 +56,8 @@ export const loadMmeSubmissions = () => loadCurrentProjectChildEntities('mme sub
const loadFamilyNotes = () => loadCurrentProjectChildEntities('family notes', REQUEST_FAMILIES, RECEIVE_FAMILIES)
-const loadSamples = () => loadCurrentProjectChildEntities('samples', REQUEST_INDIVIDUALS)
-
export const loadProjectExportData = () => (dispatch, getState) => Promise.all([
loadIndividuals()(dispatch, getState),
- loadSamples()(dispatch, getState),
loadFamilyNotes()(dispatch, getState),
])
diff --git a/ui/pages/Project/selectors.js b/ui/pages/Project/selectors.js
index 7b5b560511..70a132444a 100644
--- a/ui/pages/Project/selectors.js
+++ b/ui/pages/Project/selectors.js
@@ -12,9 +12,11 @@ import {
INDIVIDUAL_HAS_DATA_FIELD,
MME_TAG_NAME,
TISSUE_DISPLAY,
+ PRODUCT_DISPLAY,
SIMPLIFIED_SEX_LOOKUP,
} from 'shared/utils/constants'
import { toCamelcase, toSnakecase, snakecaseToTitlecase } from 'shared/utils/stringUtils'
+import { compareObjects } from 'shared/utils/sortUtils'
import {
getProjectsByGuid, getFamiliesGroupedByProjectGuid, getIndividualsByGuid, getGenesById, getUser,
@@ -23,7 +25,7 @@ import {
getVariantTagsByGuid, getUserOptionsByUsername, getNotesByFamilyType,
getVariantTagNotesByFamilyVariants, getPhenotypeGeneScoresByIndividual, getActiveDatasetsByIndividual,
getRnaSeqDataByIndividual, familyPassesFilters, getAnalysisGroupGuid, getCurrentAnalysisGroupFamilyGuids,
- getDatasetsByIndividual, getActiveDatasetsByFamily, getMinMaxDatasetsByFamily,
+ getDatasetsByIndividual, getActiveDatasetsByFamily, getMinMaxDatasetsByFamily, getDatasetsGroupedByProjectGuid,
} from 'redux/selectors'
import {
@@ -170,9 +172,32 @@ export const getProjectAnalysisGroupIndividualsByGuid = createSelector(
)
export const getProjectAnalysisGroupSamplesByTypes = createSelector(
+ getProjectGuid,
+ getCurrentAnalysisGroupFamilyGuids,
+ getProjectAnalysisGroupIndividualsByGuid,
+ getDatasetsGroupedByProjectGuid,
+ (projectGuid, analysisGroupFamilyGuids, analysisGroupIndividuals, datasetsByProject) => Object.values(
+ datasetsByProject[projectGuid] || {},
+ ).sort(compareObjects('loadedDate')).reduce((acc, { sampleType, datasetType, loadedDate, activeIndividuals, inactiveIndividuals }) => {
+ const key = `${sampleType}__${datasetType}`
+ if (!acc[key]) {
+ acc[key] = []
+ }
+ const individualGuids = [...(activeIndividuals || []), ...(inactiveIndividuals || [])]
+ const count = !analysisGroupFamilyGuids ? individualGuids.length : individualGuids.filter(
+ individualGuid => individualGuid in analysisGroupIndividuals,
+ ).length
+ if (count) {
+ acc[key].push({ loadedDate, count })
+ }
+ return acc
+ }, {}),
+)
+
+export const getProjectAnalysisGroupRnaSamplesByTypes = createSelector(
getCurrentProject,
getCurrentAnalysisGroupFamilyGuids,
- (project, analysisGroupFamilyGuids) => Object.entries(project.sampleCounts || {}).map(
+ (project, analysisGroupFamilyGuids) => Object.entries(project.rnaSampleCounts || {}).map(
([key, typeCounts]) => ([key, typeCounts.map(({ familyCounts, ...data }) => ({
...data,
count: Object.entries(familyCounts).reduce((total, [familyGuid, count]) => (
@@ -831,17 +856,19 @@ export const getTissueOptionsByIndividualGuid = createSelector(
const tissueTypesByIndividualGuid = Object.entries(rnaSeqDataByIndividualGuid || {}).map(
([individualGuid, rnaSeqData]) => ([
individualGuid,
- [...new Set(Object.values(rnaSeqData || {}).map(Object.values).flat(2).map(({ tissueType }) => tissueType))],
+ [...new Set(Object.values(rnaSeqData || {}).map(Object.values).flat(2).map(
+ ({ tissueType, sequencingType }) => `${tissueType}-${sequencingType}`,
+ ))],
]),
)
- return tissueTypesByIndividualGuid.reduce((acc, [individualGuid, tissueTypes]) => (
- tissueTypes.length > 0 ? {
- ...acc,
- [individualGuid]: tissueTypes.map(tissueType => (
- { key: tissueType, text: TISSUE_DISPLAY[tissueType] || 'Unknown Tissue', value: tissueType }
- )),
- } : acc
- ), {})
+ return tissueTypesByIndividualGuid.reduce((acc, [individualGuid, dataTypes]) => ({
+ ...acc,
+ [individualGuid]: dataTypes.map(dataType => ({
+ key: dataType,
+ value: dataType,
+ text: `Tissue type: ${TISSUE_DISPLAY[dataType.split('-')[0]] || 'Unknown Tissue'}, Sequencing Product: ${PRODUCT_DISPLAY[dataType.split('-')[1]] || 'Unknown Product'}`,
+ })),
+ }), {})
},
)
diff --git a/ui/pages/Search/components/VariantSearchResults.jsx b/ui/pages/Search/components/VariantSearchResults.jsx
index ab86291822..8ce1167fe0 100644
--- a/ui/pages/Search/components/VariantSearchResults.jsx
+++ b/ui/pages/Search/components/VariantSearchResults.jsx
@@ -43,10 +43,10 @@ const LargeRow = styled(Grid.Row)`
const scrollToTop = () => window.scrollTo(0, 0)
-const DisplayVariants = React.memo(({ displayVariants, compoundHetToggle }) => (
+const DisplayVariants = React.memo(({ displayVariants, ...props }) => (
-
+
))
@@ -95,7 +95,7 @@ const BaseVariantSearchResultsContent = React.memo(({
,
- ,
+ ,
diff --git a/ui/pages/Search/fixtures.js b/ui/pages/Search/fixtures.js
index 49bbe7a4f2..013130e72e 100644
--- a/ui/pages/Search/fixtures.js
+++ b/ui/pages/Search/fixtures.js
@@ -158,59 +158,33 @@ export const STATE = {
paternalId: '',
},
},
- samplesByGuid: {
+ datasetsByGuid: {
S2310658_wal_mc16200_mc16203: {
- createdDate: "2018-03-30T11:50:40.079Z",
- elasticsearchIndex: "CMG_MYOSEQ",
datasetType: "SNV_INDEL",
- individualGuid: "I021476_na19678",
+ activeIndividuals: ["I021476_na19678"],
+ inactiveIndividuals: [],
loadedDate: "2018-03-13T13:25:21.551Z",
projectGuid: PROJECT_GUID,
- familyGuid: FAMILY_GUID,
- sampleGuid: "S2310656_wal_mc16200_mc16203",
- sampleId: "WAL_MC16200_MC16203",
- isActive: true,
+ datasetGuid: "S2310658_wal_mc16200_mc16203",
sampleType: "WES",
},
S2310657_wal_mc16200_mc16203: {
- createdDate: "2018-03-30T11:50:40.079Z",
- elasticsearchIndex: "CMG_MYOSEQ",
datasetType: "SV",
- individualGuid: "I021476_na19678",
+ activeIndividuals: ["I021476_na19678"],
+ inactiveIndividuals: null,
loadedDate: "2018-03-13T13:25:21.551Z",
projectGuid: PROJECT_GUID,
- sampleGuid: "S2310656_wal_mc16200_mc16203",
- sampleId: "WAL_MC16200_MC16203",
- isActive: true,
+ datasetGuid: "S2310657_wal_mc16200_mc16203",
sampleType: "WES",
},
S2310656_wal_mc16200_mc16203: {
- createdDate: "2018-03-30T11:50:40.079Z",
- elasticsearchIndex: "CMG_MYOSEQ",
datasetType: "SNV_INDEL",
- individualGuid: "I021476_na19678",
+ inactiveIndividuals: ["I021476_na19678"],
loadedDate: "2018-03-13T13:25:21.551Z",
projectGuid: PROJECT_GUID,
- familyGuid: FAMILY_GUID,
- sampleGuid: "S2310656_wal_mc16200_mc16203",
- sampleId: "WAL_MC16200_MC16203",
- isActive: false,
+ datasetGuid: "S2310656_wal_mc16200_mc16203",
sampleType: "WES",
},
- S2310659_wal_mc16200_mc16203: {
- createdDate: "2018-03-30T11:50:40.079Z",
- elasticsearchIndex: null,
- datasetName: null,
- datasetType: "SNV_INDEL",
- individualGuid: "I021476_na19678",
- loadedDate: "2018-03-13T13:25:21.551Z",
- projectGuid: PROJECT_GUID,
- familyGuid: FAMILY_GUID,
- sampleGuid: "S2310656_wal_mc16200_mc16203",
- sampleId: "WAL_MC16200_MC16203",
- isActive: true,
- sampleType: "RNA",
- },
},
analysisGroupsByGuid: {
[ANALYSIS_GROUP_GUID]: {
diff --git a/ui/redux/rootReducer.js b/ui/redux/rootReducer.js
index 99bf5f2136..ccb080601b 100644
--- a/ui/redux/rootReducer.js
+++ b/ui/redux/rootReducer.js
@@ -204,12 +204,12 @@ const navigateSavedHashedSearchHelper = (resultsPath, hashKey) => (search, navig
export const navigateSavedHashedSearch = navigateSavedHashedSearchHelper('results', 'searchesByHash')
export const navigateFamiliesSearch = navigateSavedHashedSearchHelper('families', 'searchFamiliesByHash')
-const updateSavedVariant = (values, action = 'create') => (dispatch, getState) => new HttpRequestHelper(
+const updateSavedVariant = (values, action = 'create') => dispatch => new HttpRequestHelper(
`/api/saved_variant/${action}`,
(responseJson) => {
dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
},
-).post({ searchHash: getState().currentSearchHash, ...values })
+).post(values)
export const updateVariantNote = (values) => {
if (values.variantGuids) {
@@ -262,7 +262,7 @@ const rootReducer = combineReducers({
familyNotesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'familyNotesByGuid'),
familyDetailsLoading: createSingleObjectReducer(REQUEST_FAMILY_DETAILS),
individualsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'individualsByGuid'),
- samplesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'samplesByGuid'),
+ datasetsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'datasetsByGuid'),
igvSamplesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'igvSamplesByGuid'),
analysisGroupsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'analysisGroupsByGuid'),
analysisGroupsLoading: loadingReducer(REQUEST_ANALYSIS_GROUPS, RECEIVE_ANALYSIS_GROUPS),
diff --git a/ui/redux/selectors.js b/ui/redux/selectors.js
index 97ef046a96..e9b293b467 100644
--- a/ui/redux/selectors.js
+++ b/ui/redux/selectors.js
@@ -11,7 +11,7 @@ export const getFamiliesByGuid = state => state.familiesByGuid
export const getFamilyNotesByGuid = state => state.familyNotesByGuid
export const getFamilyDetailsLoading = state => state.familyDetailsLoading
export const getIndividualsByGuid = state => state.individualsByGuid
-const getSamplesByGuid = state => state.samplesByGuid
+const getDatasetsByGuid = state => state.datasetsByGuid
export const getIgvSamplesByGuid = state => state.igvSamplesByGuid
export const getAnalysisGroupsByGuid = state => state.analysisGroupsByGuid
export const getAnalysisGroupIsLoading = state => state.analysisGroupsLoading.isLoading
@@ -58,6 +58,7 @@ const groupEntitiesByProjectGuid = entities => Object.entries(entities).reduce((
}, {})
export const getFamiliesGroupedByProjectGuid = createSelector(getFamiliesByGuid, groupEntitiesByProjectGuid)
export const getAnalysisGroupsGroupedByProjectGuid = createSelector(getAnalysisGroupsByGuid, groupEntitiesByProjectGuid)
+export const getDatasetsGroupedByProjectGuid = createSelector(getDatasetsByGuid, groupEntitiesByProjectGuid)
const groupByFamilyGuid = objs => objs.reduce((acc, o) => {
if (!acc[o.familyGuid]) {
@@ -69,14 +70,14 @@ const groupByFamilyGuid = objs => objs.reduce((acc, o) => {
export const getProjectDatasetTypes = createSelector(
getProjectsByGuid,
- getSamplesByGuid,
- (projectsByGuid, samplesByGuid) => {
+ getDatasetsByGuid,
+ (projectsByGuid, datasetsByGuid) => {
const projectDatasetTypes = Object.values(projectsByGuid).reduce(
(acc, { projectGuid, datasetTypes }) => ({ ...acc, [projectGuid]: datasetTypes }), {},
)
- const sampleDatasetTypes = Object.values(samplesByGuid).reduce(
- (acc, { projectGuid, datasetType, isActive }) => {
- if (projectDatasetTypes[projectGuid] || !isActive) {
+ const sampleDatasetTypes = Object.values(datasetsByGuid).reduce(
+ (acc, { projectGuid, datasetType, activeIndividuals }) => {
+ if (projectDatasetTypes[projectGuid] || !(activeIndividuals || []).length) {
return acc
}
if (!acc[projectGuid]) {
@@ -91,15 +92,25 @@ export const getProjectDatasetTypes = createSelector(
)
export const getDatasetsByIndividual = createSelector(
- getSamplesByGuid,
- samplesByGuid => Object.values(samplesByGuid).sort(
+ getDatasetsByGuid,
+ datasetsByGuid => Object.values(datasetsByGuid).sort(
(a, b) => a.loadedDate.localeCompare(b.loadedDate),
- ).reduce((acc, sample) => {
- const { individualGuid, isActive, sampleType, datasetType, loadedDate } = sample
- if (!acc[individualGuid]) {
- acc[individualGuid] = []
- }
- acc[individualGuid].push({ isActive, sampleType, datasetType, loadedDate: loadedDate.split('T')[0] })
+ ).reduce((acc, { activeIndividuals, inactiveIndividuals, sampleType, datasetType, loadedDate }) => {
+ const parsedLoadedDate = loadedDate.split('T')[0]
+ const activeIndivs = activeIndividuals || []
+ activeIndivs.forEach((individualGuid) => {
+ if (!acc[individualGuid]) {
+ acc[individualGuid] = []
+ }
+ acc[individualGuid].push({ isActive: true, sampleType, datasetType, loadedDate: parsedLoadedDate })
+ })
+ const inactiveIndivs = inactiveIndividuals || []
+ inactiveIndivs.forEach((individualGuid) => {
+ if (!acc[individualGuid]) {
+ acc[individualGuid] = []
+ }
+ acc[individualGuid].push({ isActive: false, sampleType, datasetType, loadedDate: parsedLoadedDate })
+ })
return acc
}, {}),
)
diff --git a/ui/shared/components/panel/MatchmakerPanel.test.js b/ui/shared/components/panel/MatchmakerPanel.test.js
index bdba5540d4..8a30161006 100644
--- a/ui/shared/components/panel/MatchmakerPanel.test.js
+++ b/ui/shared/components/panel/MatchmakerPanel.test.js
@@ -12,7 +12,7 @@ test('shallow-render SubmissionGeneVariants without crashing', () => {
const store = configureStore()(STATE1)
shallow()
})
diff --git a/ui/shared/components/panel/fixtures.js b/ui/shared/components/panel/fixtures.js
index 4bca6989a7..f05658a765 100644
--- a/ui/shared/components/panel/fixtures.js
+++ b/ui/shared/components/panel/fixtures.js
@@ -374,7 +374,7 @@ export const STATE1 = {
},
},
analysisGroupsByGuid: {},
- samplesByGuid: {},
+ datasetsByGuid: {},
igvSamplesByGuid: {
S2310656_wal_mc16200_mc16203: {
projectGuid: 'R0237_1000_genomes_demo',
diff --git a/ui/shared/components/panel/variants/FamilyVariantTags.jsx b/ui/shared/components/panel/variants/FamilyVariantTags.jsx
index 2d53cc6f41..16b888e6ec 100644
--- a/ui/shared/components/panel/variants/FamilyVariantTags.jsx
+++ b/ui/shared/components/panel/variants/FamilyVariantTags.jsx
@@ -108,7 +108,14 @@ export const taggedByPopup = (tag, title) => (trigger, hideMetadata) => (
) : {tag.metadata}}
)}
- {tag.searchHash && Re-run search
}
+ {tag.searchHash && (
+
+
+ Re-run
+ {tag.searchName ? `search: "${tag.searchName}"` : 'custom search'}
+
+
+ )}
}
/>
@@ -393,10 +400,14 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatchUpdateVariantNote: updates => dispatch(
- updateVariantNote({ ...updates, variant: ownProps.variant, familyGuid: ownProps.familyGuid }),
+ updateVariantNote({
+ ...updates, variant: ownProps.variant, searchHash: ownProps.searchHash, familyGuid: ownProps.familyGuid,
+ }),
),
dispatchUpdateFamilyVariantTags: updates => dispatch(
- updateVariantTags({ ...updates, variant: ownProps.variant, familyGuid: ownProps.familyGuid }),
+ updateVariantTags({
+ ...updates, variant: ownProps.variant, searchHash: ownProps.searchHash, familyGuid: ownProps.familyGuid,
+ }),
),
dispatchUpdateFamilyVariantFunctionalTags: updates => dispatch(
updateVariantTags({ ...updates, variant: ownProps.variant, familyGuid: ownProps.familyGuid }, 'functional_data'),
diff --git a/ui/shared/components/panel/variants/RnaSeqTpm.jsx b/ui/shared/components/panel/variants/RnaSeqTpm.jsx
index fe1612a680..6185d61d3a 100644
--- a/ui/shared/components/panel/variants/RnaSeqTpm.jsx
+++ b/ui/shared/components/panel/variants/RnaSeqTpm.jsx
@@ -4,17 +4,17 @@ import { extent, max, median, min, quantile } from 'd3-array'
import { scaleBand, scaleLinear } from 'd3-scale'
import { HttpRequestHelper } from 'shared/utils/httpRequestHelper'
-import { TISSUE_DISPLAY } from 'shared/utils/constants'
+import { TISSUE_DISPLAY, PRODUCT_DISPLAY } from 'shared/utils/constants'
import { initializeD3, Tooltip } from 'shared/components/graph/d3Utils'
import GtexLauncher from 'shared/components/graph/GtexLauncher'
const BOX_WIDTH = 100
-const PLOT_WIDTH = 550
+const PLOT_WIDTH = 500
const PLOT_HEIGHT = 350
const MARGINS = {
left: 40,
top: 0,
- bottom: 100,
+ bottom: 130,
}
// Code adapted from https://github.com/broadinstitute/gtex-viz/blob/8d65862fbe7e5ab9b4d5be419568754e0d17bb07/src/modules/Boxplot.js
@@ -56,7 +56,7 @@ const renderBoxplot = (allData, containerElement) => {
const svg = initializeD3(
containerElement,
{ width: PLOT_WIDTH, height: PLOT_HEIGHT },
- { ...MARGINS, right: bandwidth / 2 + 10 },
+ { ...MARGINS, right: bandwidth / 2 + 60 },
scales,
{ x: { offset: bandwidth / 2 }, y: { text: 'TPM' } },
)
@@ -189,17 +189,16 @@ const renderGtex = (gtexExpressionData, familyExpressionData, containerElement)
const gtexByTissue = ((gtexExpressionData || {}).data || []).reduce((acc, { data, tissueSiteDetailId }) => ({
...acc, [GTEX_TISSUE_LOOKUP[tissueSiteDetailId]]: data,
}), {})
- const boxplotData = Object.entries(familyExpressionData).reduce((
- acc, [tissue, { rdgData, myData, individualData }],
- ) => ([
- ...acc,
- ...[
- { data: gtexByTissue[tissue], label: 'GTEx' },
- { data: rdgData, label: 'Broad RDG' },
- { data: myData, label: 'My Data' },
- ...Object.entries(individualData).map(([individual, tpm]) => ({ data: [tpm], label: individual, medianColor: '#000080' })),
- ].filter(({ data }) => data).map(({ label, ...d }) => ({ label: `${label} - ${TISSUE_DISPLAY[tissue]}`, ...d })),
- ]), [])
+ const boxplotData = Object.entries(familyExpressionData).reduce((acc, [tissue, tissueData]) => ([
+ ...acc, ...[
+ { data: gtexByTissue[tissue], label: `GTEx - ${TISSUE_DISPLAY[tissue]}` },
+ ...Object.entries(tissueData).reduce((tAcc, [sequencingProduct, { rdgData, myData, individualData }]) => ([
+ ...tAcc,
+ { data: rdgData, label: 'Broad RDG' },
+ { data: myData, label: 'My Data' },
+ ...Object.entries(individualData).map(([individual, tpm]) => ({ data: [tpm], label: individual, medianColor: '#000080' })),
+ ].filter(({ data }) => data).map(({ label, ...d }) => ({ label: `${label} - ${TISSUE_DISPLAY[tissue]} - ${PRODUCT_DISPLAY[sequencingProduct]}`, ...d }))), []),
+ ]]), [])
renderBoxplot(boxplotData, containerElement)
}
diff --git a/ui/shared/components/panel/variants/Variants.jsx b/ui/shared/components/panel/variants/Variants.jsx
index 7bad475092..bd16675dfa 100644
--- a/ui/shared/components/panel/variants/Variants.jsx
+++ b/ui/shared/components/panel/variants/Variants.jsx
@@ -78,7 +78,7 @@ const tagFamily = tag => (
const VariantLayout = (
{
variant, compoundHetToggle, mainGeneId, isCompoundHet, linkToSavedVariants, topContent,
- bottomContent, children, ...rowProps
+ bottomContent, children, searchHash, ...rowProps
},
) => {
const coreVariant = Array.isArray(variant) ? variant[0] : variant
@@ -95,6 +95,7 @@ const VariantLayout = (
variant={variant}
isCompoundHet={isCompoundHet}
linkToSavedVariants={linkToSavedVariants}
+ searchHash={searchHash}
/>
@@ -131,6 +132,7 @@ VariantLayout.propTypes = {
topContent: PropTypes.node,
bottomContent: PropTypes.node,
children: PropTypes.node,
+ searchHash: PropTypes.string,
}
export const Variant = React.memo((
diff --git a/ui/shared/components/panel/view-pedigree-image/LazyPedigreeImagePanel.jsx b/ui/shared/components/panel/view-pedigree-image/LazyPedigreeImagePanel.jsx
index 5662693c92..3e445935a3 100644
--- a/ui/shared/components/panel/view-pedigree-image/LazyPedigreeImagePanel.jsx
+++ b/ui/shared/components/panel/view-pedigree-image/LazyPedigreeImagePanel.jsx
@@ -201,26 +201,24 @@ class BasePedigreeImage extends React.PureComponent {
dataset.filter(
({ mother, father }) => mother || father,
).forEach((indiv) => {
- if (indiv.mother && indiv.father) {
- // pedigree js incorrectly considers people whose parents are different generations consanguineous
- const mother = nameMap[indiv.mother]
- const father = nameMap[indiv.father]
- let missingGenParent = null
- if (mother.top_level && !father.top_level) {
- missingGenParent = mother
- } else if (father.top_level && !mother.top_level) {
- missingGenParent = father
- }
- if (missingGenParent) {
- missingGenParent.top_level = false
- this.addParent(`${missingGenParent.name}-father-placeholder`, missingGenParent, newParents, true)
- this.addParent(`${missingGenParent.name}-mother-placeholder`, missingGenParent, newParents, false)
- }
- return
+ if (!indiv.mother || !indiv.father) {
+ const placeholderId = `${indiv.mother || indiv.father}-spouse-placeholder`
+ this.addParent(placeholderId, indiv, newParents, !indiv.father)
+ }
+ // pedigree js incorrectly considers people whose parents are different generations consanguineous
+ const mother = nameMap[indiv.mother] || newParents[indiv.mother]
+ const father = nameMap[indiv.father] || newParents[indiv.father]
+ let missingGenParent = null
+ if (mother.top_level && !father.top_level) {
+ missingGenParent = mother
+ } else if (father.top_level && !mother.top_level) {
+ missingGenParent = father
+ }
+ if (missingGenParent) {
+ missingGenParent.top_level = false
+ this.addParent(`${missingGenParent.name}-father-placeholder`, missingGenParent, newParents, true)
+ this.addParent(`${missingGenParent.name}-mother-placeholder`, missingGenParent, newParents, false)
}
-
- const placeholderId = `${indiv.mother || indiv.father}-spouse-placeholder`
- this.addParent(placeholderId, indiv, newParents, !indiv.father)
})
return this.yobToAge([...dataset, ...Object.values(newParents)])
diff --git a/ui/shared/utils/constants.js b/ui/shared/utils/constants.js
index 40a82f0f53..908acc8294 100644
--- a/ui/shared/utils/constants.js
+++ b/ui/shared/utils/constants.js
@@ -2119,6 +2119,12 @@ export const TISSUE_DISPLAY = {
B: 'Brain',
}
+export const PRODUCT_DISPLAY = {
+ K: 'Kinnex',
+ T: 'TruSeq',
+ W: 'Watchmaker',
+}
+
export const RNASEQ_JUNCTION_PADDING = 200
export const FAQ_PATH = '/faq'
diff --git a/vlm/test_vlm.py b/vlm/test_vlm.py
index 7889cddbeb..9b0a1f02e3 100644
--- a/vlm/test_vlm.py
+++ b/vlm/test_vlm.py
@@ -32,7 +32,7 @@ def inject_fixtures(self, caplog):
async def test_match(self, mocked_responses):
mocked_responses.post(
- 'https://vlm-auth.us.auth0.com/oauth/token', payload={'access_token': 'test_token'}, repeat=True,
+ 'https://vlm-auth.us.auth0.com/oauth/token', payload={'access_token': 'test_token'}, repeat=True, # nosec
)
mocked_responses.get(
f'https://vlm-auth.us.auth0.com/api/v2/clients/{REQUESTER_CLIENT_ID}',
@@ -106,7 +106,7 @@ async def test_match(self, mocked_responses):
mocked_responses.assert_called_with(
'https://vlm-auth.us.auth0.com/oauth/token',
method='POST',
- json={'client_id': 'unknown_client_id', 'client_secret': 'unknown_client_secret', 'audience': 'https://vlm-auth.us.auth0.com/api/v2/', 'grant_type': 'client_credentials'},
+ json={'client_id': 'unknown_client_id', 'client_secret': 'unknown_client_secret', 'audience': 'https://vlm-auth.us.auth0.com/api/v2/', 'grant_type': 'client_credentials'}, # nosec
)
mocked_responses.assert_called_with(
f'https://vlm-auth.us.auth0.com/api/v2/clients/{REQUESTER_CLIENT_ID}',
@@ -292,7 +292,7 @@ async def test_match_error(self, mocked_responses):
self.assertEqual(resp.reason, 'Credential Check Error')
self.assertEqual(self._caplog.messages[-3], "Credential Check Error: 400 - {'error': 'invalid_request'}")
- mocked_responses.post('https://vlm-auth.us.auth0.com/oauth/token', payload={'access_token': 'test_token'}, repeat=True)
+ mocked_responses.post('https://vlm-auth.us.auth0.com/oauth/token', payload={'access_token': 'test_token'}, repeat=True) # nosec
async with self.client.request('GET', '/vlm/match', headers=headers) as resp:
self.assertEqual(resp.status, 403)
self.assertEqual(resp.reason, 'Invalid Client ID abc123')
diff --git a/wsgi.py b/wsgi.py
index 1fd7e20bda..a8f0b255bc 100644
--- a/wsgi.py
+++ b/wsgi.py
@@ -1,18 +1,3 @@
-"""
-WSGI config for xbrowse_server project.
-
-This module contains the WSGI application used by Django's development server
-and any production WSGI deployments. It should expose a module-level variable
-named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
-this application via the ``WSGI_APPLICATION`` setting.
-
-Usually you will have the standard Django WSGI application here, but it also
-might make sense to replace the whole Django WSGI application with a custom one
-that later delegates to the Django one. For example, you could introduce WSGI
-middleware here, or combine a Django application with an application of another
-framework.
-
-"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")