@@ -88,8 +85,6 @@ const SAVED_VARIANT_COLUMNS = [
]
const SAVED_SV_COLUMNS = [{ name: 'svType', content: 'SV Type', width: 2 }, ...SAVED_VARIANT_COLUMNS]
-const GENOME_FIELD = { ...GENOME_VERSION_FIELD, inline: false, width: null }
-
const ZYGOSITY_FIELD = {
name: 'genotypes',
component: connect(mapZygosityInputStateToProps)(ZygosityInput),
@@ -127,7 +122,6 @@ const GENE_FIELD = {
const SAVED_VARIANT_FIELD = {
name: VARIANTS_FIELD_NAME,
idField: 'variantGuid',
- includeSelectedRowData: true,
control: SavedVariantField,
}
@@ -156,7 +150,6 @@ const SNV_FIELDS = [
{ name: TRANSCRIPT_ID_FIELD_NAME, label: 'Transcript ID', width: 6 },
{ name: HGVSC_FIELD_NAME, label: 'HGVSC', width: 5, validate: validateHasTranscriptId },
{ name: HGVSP_FIELD_NAME, label: 'HGVSP', width: 5, validate: validateHasTranscriptId },
- GENOME_FIELD,
TAG_FIELD,
{ accordionLabel: 'Multinucleotide Variant', columns: SAVED_VARIANT_COLUMNS, ...SAVED_VARIANT_FIELD },
{
@@ -176,7 +169,6 @@ const SV_FIELDS = [
START_FIELD,
END_FIELD,
GENE_FIELD,
- GENOME_FIELD,
TAG_FIELD,
{ name: SV_FIELD_NAME, validate: validators.required, label: 'SV Name', width: 8 },
{
@@ -206,7 +198,6 @@ const BaseCreateVariantButton = React.memo(({ variantType, family, user, project
(project.isAnalystProject ? user.isAnalyst : project.canEdit) ? (
({
})
const mapDispatchToProps = (dispatch, ownProps) => ({
- onSubmit: (values) => {
- const variant = ownProps.formFields.map(({ name }) => name).filter(
- name => !FORMAT_RESPONSE_FIELDS.includes(name),
- ).reduce(
- (acc, name) => ({ ...acc, [name]: values[name] }), {},
- )
-
- if (variant.svName) {
- variant.variantId = values.svName
- if (values[GENE_ID_FIELD_NAME]) {
- variant.transcripts = { [values[GENE_ID_FIELD_NAME]]: [] }
- }
- } else {
- variant.variantId = `${values.chrom}-${values.pos}-${values.ref}-${values.alt}`
- variant.transcripts = {
- [values[GENE_ID_FIELD_NAME]]: values[TRANSCRIPT_ID_FIELD_NAME] ? [{
- transcriptId: values[TRANSCRIPT_ID_FIELD_NAME],
- [HGVSC_FIELD_NAME]: values[HGVSC_FIELD_NAME],
- [HGVSP_FIELD_NAME]: values[HGVSP_FIELD_NAME],
- }] : [],
- }
- }
-
- const variants = Object.values(values[VARIANTS_FIELD_NAME] || {}).filter(v => v)
-
- const formattedValues = {
- familyGuid: ownProps.family.familyGuid,
- tags: values[TAG_FIELD_NAME],
- variant: [variant, ...variants],
- }
-
- return dispatch(updateVariantTags(formattedValues))
- },
+ onSubmit: values => dispatch(createManualVariant(ownProps.family.familyGuid, values)),
})
const CreateVariantButton = connect(mapStateToProps, mapDispatchToProps)(BaseCreateVariantButton)
diff --git a/ui/pages/Project/components/FamilyTable/IndividualRow.jsx b/ui/pages/Project/components/FamilyTable/IndividualRow.jsx
index 3f796b1df5..2d4b134359 100644
--- a/ui/pages/Project/components/FamilyTable/IndividualRow.jsx
+++ b/ui/pages/Project/components/FamilyTable/IndividualRow.jsx
@@ -5,7 +5,6 @@ import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { Field } from 'react-final-form'
import { Label, Popup, Form, Input, Loader } from 'semantic-ui-react'
-import orderBy from 'lodash/orderBy'
import { SearchInput, YearSelector, RadioButtonGroup, ButtonRadioGroup, Select } from 'shared/components/form/Inputs'
import { helpLabel, validators } from 'shared/components/form/FormHelpers'
@@ -32,7 +31,7 @@ import {
import { snakecaseToTitlecase } from 'shared/utils/stringUtils'
import { updateIndividual } from 'redux/rootReducer'
-import { getSamplesByGuid, getMmeSubmissionsByGuid, getIGVSamplesByFamilySampleIndividual } from 'redux/selectors'
+import { getDatasetsByIndividual, getMmeSubmissionsByGuid, getIGVSamplesByFamilySampleIndividual } from 'redux/selectors'
import { HPO_FORM_FIELDS } from '../HpoTerms'
import {
CASE_REVIEW_STATUS_MORE_INFO_NEEDED, CASE_REVIEW_STATUS_OPTIONS, CASE_REVIEW_TABLE_NAME, INDIVIDUAL_DETAIL_FIELDS,
@@ -149,10 +148,10 @@ MmeStatusLabel.propTypes = {
mmeSubmission: PropTypes.object,
}
-const DataDetails = React.memo(({ loadedSamples, individual, mmeSubmission }) => (
+const DataDetails = React.memo(({ loadedDatasets, individual, mmeSubmission }) => (
- {loadedSamples.map(
- sample =>
,
+ {loadedDatasets.map(
+ dataset =>
,
)}
{individual.rnaSample && (
DataDetails.propTypes = {
mmeSubmission: PropTypes.object,
individual: PropTypes.object,
- loadedSamples: PropTypes.arrayOf(PropTypes.object),
+ loadedDatasets: PropTypes.arrayOf(PropTypes.object),
}
const formatGene = gene => `${gene.gene} ${gene.comments ? ` (${gene.comments.trim()})` : ''}`
@@ -548,7 +547,7 @@ class IndividualRow extends React.PureComponent {
project: PropTypes.object.isRequired,
individual: PropTypes.object.isRequired,
mmeSubmission: PropTypes.object,
- samplesByGuid: PropTypes.object.isRequired,
+ datasets: PropTypes.arrayOf(PropTypes.object),
alignmentSample: PropTypes.object,
dispatchUpdateIndividual: PropTypes.func,
dispatchUpdateIndividualIGV: PropTypes.func,
@@ -575,17 +574,15 @@ class IndividualRow extends React.PureComponent {
render() {
const {
- project, individual, mmeSubmission, samplesByGuid, tableName, updateIndividualPedigree, alignmentSample,
+ project, individual, mmeSubmission, datasets, tableName, updateIndividualPedigree, alignmentSample,
dispatchUpdateIndividualIGV,
} = this.props
- const { displayName, sex, affected, createdDate, sampleGuids } = individual
+ const { displayName, sex, affected, createdDate } = individual
- let loadedSamples = sampleGuids.map(
- sampleGuid => samplesByGuid[sampleGuid],
- )
- loadedSamples = orderBy(loadedSamples, [s => s.loadedDate], 'desc')
// only show active or first/ last inactive samples
- loadedSamples = loadedSamples.filter((sample, i) => sample.isActive || i === 0 || i === loadedSamples.length - 1)
+ const loadedDatasets = (datasets || []).reverse().filter(
+ ({ isActive }, i) => isActive || i === 0 || i === datasets.length - 1,
+ )
const leftContent = (
@@ -622,7 +619,7 @@ class IndividualRow extends React.PureComponent {
const rightContent = editCaseReview ?
: (
@@ -646,7 +643,7 @@ export { IndividualRow as IndividualRowComponent }
const mapStateToProps = (state, ownProps) => ({
project: getCurrentProject(state),
- samplesByGuid: getSamplesByGuid(state),
+ datasets: getDatasetsByIndividual(state)[ownProps.individual.individualGuid],
mmeSubmission: getMmeSubmissionsByGuid(state)[ownProps.individual.mmeSubmissionGuid],
alignmentSample: (
getIGVSamplesByFamilySampleIndividual(state)[ownProps.individual.familyGuid]?.alignment || {}
diff --git a/ui/pages/Project/constants.js b/ui/pages/Project/constants.js
index 4c2e4c11c6..be1991a1f7 100644
--- a/ui/pages/Project/constants.js
+++ b/ui/pages/Project/constants.js
@@ -367,18 +367,16 @@ export const FAMILY_SORT_OPTIONS = [
{
value: SORT_BY_DATA_LOADED_DATE,
name: 'Date Loaded',
- createSortKeyGetter: (individualsByGuid, samplesByFamily) => (family) => {
- const loadedSamples = samplesByFamily[family.familyGuid] || []
- return loadedSamples.length ? loadedSamples[loadedSamples.length - 1].loadedDate : '2000-01-01T01:00:00.000Z'
- },
+ createSortKeyGetter: (individualsByGuid, minMaxDatasetsByFamily) => family => (
+ minMaxDatasetsByFamily[family.familyGuid][1]?.loadedDate || '2000-01-01T01:00:00.000Z'
+ ),
},
{
value: SORT_BY_DATA_FIRST_LOADED_DATE,
name: 'Date First Loaded',
- createSortKeyGetter: (individualsByGuid, samplesByFamily) => (family) => {
- const loadedSamples = samplesByFamily[family.familyGuid] || []
- return loadedSamples.length ? loadedSamples[0].loadedDate : '2000-01-01T01:00:00.000Z'
- },
+ createSortKeyGetter: (individualsByGuid, minMaxDatasetsByFamily) => family => (
+ minMaxDatasetsByFamily[family.familyGuid][0]?.loadedDate || '2000-01-01T01:00:00.000Z'
+ ),
},
{
value: SORT_BY_ANALYSIS_STATUS,
@@ -506,12 +504,12 @@ export const CASE_REVIEW_INDIVIDUAL_EXPORT_DATA = [
export const SAMPLE_EXPORT_DATA = [
{ header: 'Family ID', field: 'familyId' },
{ header: 'Individual ID', field: 'individualId' },
- { header: 'Sample ID', field: 'sampleId' },
{ header: 'Loaded Date', field: 'loadedDate' },
{ header: 'Sample Type', field: 'sampleType' },
+ { header: 'Dataset Type', field: 'datasetType' },
]
-export const TAG_FIELD_NAME = 'tags'
+const TAG_FIELD_NAME = 'tags'
export const TAG_FORM_FIELD = {
name: TAG_FIELD_NAME,
diff --git a/ui/pages/Project/reducers.js b/ui/pages/Project/reducers.js
index 4a406efbc7..c2181b8dfe 100644
--- a/ui/pages/Project/reducers.js
+++ b/ui/pages/Project/reducers.js
@@ -145,6 +145,13 @@ export const loadFamilySavedVariants = familyGuid => loadSavedVariants({ familyG
export const unloadProject = () => dispatch => dispatch({ type: UPDATE_CURRENT_PROJECT, newValue: null })
+export const createManualVariant = (familyGuid, values) => dispatch => new HttpRequestHelper(
+ `/api/saved_variant/create_manual/${familyGuid}`,
+ (responseJson) => {
+ dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
+ },
+).post(values)
+
export const updateFamilies = values => (dispatch, getState) => {
const action = values.delete ? 'delete' : 'edit'
return new HttpRequestHelper(`/api/project/${getState().currentProjectGuid}/${action}_families`,
diff --git a/ui/pages/Project/selectors.js b/ui/pages/Project/selectors.js
index 2e36795d83..172b8ad08b 100644
--- a/ui/pages/Project/selectors.js
+++ b/ui/pages/Project/selectors.js
@@ -17,12 +17,13 @@ import {
import { toCamelcase, toSnakecase, snakecaseToTitlecase } from 'shared/utils/stringUtils'
import {
- getProjectsByGuid, getFamiliesGroupedByProjectGuid, getIndividualsByGuid, getSamplesByGuid, getGenesById, getUser,
+ getProjectsByGuid, getFamiliesGroupedByProjectGuid, getIndividualsByGuid, getGenesById, getUser,
getAnalysisGroupsGroupedByProjectGuid, getSavedVariantsByGuid, getSortedIndividualsByFamily,
getMmeResultsByGuid, getMmeSubmissionsByGuid, getHasActiveSearchSampleByFamily, getSelectableTagTypesByProject,
- getVariantTagsByGuid, getUserOptionsByUsername, getSamplesByFamily, getNotesByFamilyType,
- getVariantTagNotesByFamilyVariants, getPhenotypeGeneScoresByIndividual,
+ getVariantTagsByGuid, getUserOptionsByUsername, getNotesByFamilyType,
+ getVariantTagNotesByFamilyVariants, getPhenotypeGeneScoresByIndividual, getActiveDatasetsByIndividual,
getRnaSeqDataByIndividual, familyPassesFilters, getAnalysisGroupGuid, getCurrentAnalysisGroupFamilyGuids,
+ getDatasetsByIndividual, getActiveDatasetsByFamily, getMinMaxDatasetsByFamily,
} from 'redux/selectors'
import {
@@ -134,11 +135,9 @@ export const getProjectAnalysisGroupFamilySizeHistogram = createSelector(
export const getProjectAnalysisGroupDataLoadedFamilySizeHistogram = createSelector(
getProjectAnalysisGroupFamiliesByGuid,
- getSamplesByFamily,
- (familiesByGuid, samplesByFamily) => getFamilySizeHistogram(Object.values(familiesByGuid).map(((family) => {
- const sampleIndividuals = new Set((samplesByFamily[family.familyGuid] || []).filter(
- sample => sample.isActive,
- ).map(sample => sample.individualGuid))
+ getActiveDatasetsByFamily,
+ (familiesByGuid, datasetsByFamily) => getFamilySizeHistogram(Object.values(datasetsByFamily).map(((family) => {
+ const sampleIndividuals = new Set((datasetsByFamily[family.familyGuid] || []).map(sample => sample.individualGuid))
const hasSampleParents = (family.parents || []).reduce(
(acc, { individualGuid, maternalGuid, paternalGuid }) => {
const hasSampleMaternal = sampleIndividuals.has(maternalGuid)
@@ -456,16 +455,16 @@ export const getVisibleFamilies = createSelector(
export const getVisibleFamiliesInSortedOrder = createSelector(
getVisibleFamilies,
getIndividualsByGuid,
- getSamplesByFamily,
+ getMinMaxDatasetsByFamily,
getFamiliesSortOrder,
getFamiliesSortDirection,
- (visibleFamilies, individualsByGuid, samplesByFamily, familiesSortOrder, familiesSortDirection) => {
+ (visibleFamilies, individualsByGuid, minMaxDatasetsByFamily, familiesSortOrder, familiesSortDirection) => {
if (!familiesSortOrder || !FAMILY_SORT_LOOKUP[familiesSortOrder] ||
visibleFamilies.some(({ familyId }) => !familyId)) { // families have been loaded without any core fields
return visibleFamilies
}
- const getSortKey = FAMILY_SORT_LOOKUP[familiesSortOrder](individualsByGuid, samplesByFamily)
+ const getSortKey = FAMILY_SORT_LOOKUP[familiesSortOrder](individualsByGuid, minMaxDatasetsByFamily)
return visibleFamilies.slice(0).sort((a, b) => getSortKey(a).localeCompare(getSortKey(b)) * familiesSortDirection)
},
)
@@ -481,50 +480,40 @@ export const getEntityExportConfig = ({ projectName, tableName, fileName, fields
const getFamiliesExportData = createSelector(
getVisibleFamiliesInSortedOrder,
- getSamplesByFamily,
+ getMinMaxDatasetsByFamily,
getNotesByFamilyType,
- (visibleFamilies, samplesByFamily, notesByFamilyType) => visibleFamilies.reduce((acc, family) => [...acc, {
+ (visibleFamilies, minMaxDatasetsByFamily, notesByFamilyType) => visibleFamilies.reduce((acc, family) => [...acc, {
...family,
...FAMILY_NOTES_FIELDS.reduce((noteAcc, { id, noteType }) => (
{ ...noteAcc, [id]: (notesByFamilyType[family.familyGuid] || {})[noteType] }), {}),
- [FAMILY_FIELD_FIRST_SAMPLE]: (samplesByFamily[family.familyGuid] || [])[0],
+ [FAMILY_FIELD_FIRST_SAMPLE]: (minMaxDatasetsByFamily[family.familyGuid] || [])[0],
}], []),
)
-const getSamplesByIndividual = createSelector(
- getSamplesByGuid,
- samplesByGuid => Object.values(samplesByGuid).reduce((acc, sample) => {
- if (!acc[sample.individualGuid]) {
- acc[sample.individualGuid] = []
- }
- acc[sample.individualGuid].push(sample)
- return acc
- }, {}),
-)
-
const getIndividualsExportData = createSelector(
getVisibleFamiliesInSortedOrder,
getSortedIndividualsByFamily,
- getSamplesByIndividual,
- (families, individualsByFamily, samplesByIndividual) => families.reduce((acc, family) => [
+ getActiveDatasetsByIndividual,
+ (families, individualsByFamily, activeDatasetsByIndividual) => families.reduce((acc, family) => [
...acc, ...(individualsByFamily[family.familyGuid] || []).map(individual => ({
...individual,
[FAMILY_FIELD_ID]: family.familyId,
- [INDIVIDUAL_HAS_DATA_FIELD]: (
- samplesByIndividual[individual.individualGuid] || []).some(({ isActive }) => isActive),
+ [INDIVIDUAL_HAS_DATA_FIELD]: (activeDatasetsByIndividual[individual.individualGuid] || []).length > 0,
}))], []),
)
const getSamplesExportData = createSelector(
getVisibleFamiliesInSortedOrder,
- getIndividualsByGuid,
- getSamplesByFamily,
- (visibleFamilies, individualsByGuid, samplesByFamily) => visibleFamilies.reduce((acc, family) => [
- ...acc, ...(samplesByFamily[family.familyGuid] || []).map(sample => ({
- ...sample,
- [FAMILY_FIELD_ID]: family.familyId,
- [INDIVIDUAL_FIELD_ID]: individualsByGuid[sample.individualGuid]?.individualId,
- }))], []),
+ getSortedIndividualsByFamily,
+ getDatasetsByIndividual,
+ (visibleFamilies, individualsByFamily, datasetsByIndividual) => visibleFamilies.reduce((acc, family) => [
+ ...acc, ...(individualsByFamily[family.familyGuid] || []).reduce((indivAcc, { individualGuid, individualId }) => ([
+ ...indivAcc, ...(datasetsByIndividual[individualGuid] || []).map(sample => ({
+ ...sample,
+ [FAMILY_FIELD_ID]: family.familyId,
+ [INDIVIDUAL_FIELD_ID]: individualId,
+ })),
+ ]), [])], []),
)
export const getProjectExportUrls = createSelector(
diff --git a/ui/pages/Project/selectors.test.js b/ui/pages/Project/selectors.test.js
index 502fdc28f4..ce3ed0dc11 100644
--- a/ui/pages/Project/selectors.test.js
+++ b/ui/pages/Project/selectors.test.js
@@ -47,8 +47,10 @@ test('getProjectExportUrls', () => {
expect(exportFamilies.length).toEqual(2)
expect(exportFamilies[0].familyGuid).toEqual('F011652_2')
expect(exportFamilies[1].familyGuid).toEqual('F011652_1')
- expect(exportFamilies[0].firstSample.sampleGuid).toEqual('S2310656_wal_mc16200_mc16203')
- expect(exportFamilies[1].firstSample).toEqual(undefined)
+ expect(exportFamilies[0].firstSample.datasetType).toEqual('SNV_INDEL')
+ expect(exportFamilies[0].firstSample.sampleType).toEqual('WES')
+ expect(exportFamilies[0].firstSample.loadedDate).toEqual('2018-03-13')
+ expect(exportFamilies[1].firstSample).toEqual(null)
expect(exportFamilies[0].analysisNotes.length).toEqual(2)
expect(exportFamilies[0].caseNotes).toEqual(undefined)
diff --git a/ui/pages/Report/components/VariantMetadata.jsx b/ui/pages/Report/components/VariantMetadata.jsx
index 828c65f416..b077b70c36 100644
--- a/ui/pages/Report/components/VariantMetadata.jsx
+++ b/ui/pages/Report/components/VariantMetadata.jsx
@@ -10,7 +10,7 @@ const VIEW_ALL_PAGES = [
const COLUMNS = [
{ name: 'participant_id' },
- ...VARIANT_METADATA_COLUMNS.slice(0, -1),
+ ...VARIANT_METADATA_COLUMNS,
{ name: 'variant_type' },
{ name: 'allele_balance_or_heteroplasmy_percentage' },
{ name: 'Clinvar allele ID', format: ({ clinvar }) => clinvar?.alleleId },
@@ -22,7 +22,7 @@ const COLUMNS = [
{ name: 'additional_family_members_with_variant' },
{ name: 'method_of_discovery' },
{ name: 'Submitted to MME', format: ({ MME }) => (MME ? 'Yes' : 'No') },
- ...VARIANT_METADATA_COLUMNS.slice(-1),
+ { name: 'ClinGen_allele_ID' },
{ name: 'tags', format: ({ tags }) => (tags || []).join('; ') },
]
diff --git a/ui/pages/SummaryData/components/VariantLookup.jsx b/ui/pages/Search/VariantLookup.jsx
similarity index 55%
rename from ui/pages/SummaryData/components/VariantLookup.jsx
rename to ui/pages/Search/VariantLookup.jsx
index 961f236a2a..ccbb4f6982 100644
--- a/ui/pages/SummaryData/components/VariantLookup.jsx
+++ b/ui/pages/Search/VariantLookup.jsx
@@ -1,25 +1,41 @@
import React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
-import { Grid, Header, Label, Table } from 'semantic-ui-react'
+import { Grid, Header, Icon, Label, Popup, Table } from 'semantic-ui-react'
import { RECEIVE_DATA } from 'redux/utils/reducerUtils'
+import { navigateSavedHashedSearch } from 'redux/rootReducer'
import { getVlmEnabled } from 'redux/selectors'
import { QueryParamsEditor } from 'shared/components/QueryParamEditor'
import StateDataLoader from 'shared/components/StateDataLoader'
import SendEmailButton from 'shared/components/buttons/SendEmailButton'
import FormWrapper from 'shared/components/form/FormWrapper'
-import { helpLabel } from 'shared/components/form/FormHelpers'
-import { BaseSemanticInput, InlineToggle } from 'shared/components/form/Inputs'
+import { helpLabel, validators } from 'shared/components/form/FormHelpers'
+import { BaseSemanticInput, InlineToggle, AlignedCheckboxGroup } from 'shared/components/form/Inputs'
+import { AwesomeBarFormInput } from 'shared/components/page/AwesomeBar'
import FamilyReads from 'shared/components/panel/family/FamilyReads'
import FamilyVariantTags from 'shared/components/panel/variants/FamilyVariantTags'
import Variants, { Variant, StyledVariantRow } from 'shared/components/panel/variants/Variants'
import { FamilyVariantIndividuals } from 'shared/components/panel/variants/VariantIndividuals'
-import { GENOME_VERSION_FIELD, GENOME_VERSION_37, GENOME_VERSION_38 } from 'shared/utils/constants'
-import { sendVlmContactEmail } from '../reducers'
-import { getVlmDefaultContactEmails, getVlmFamiliesByContactEmail } from '../selectors'
+import {
+ GENOME_VERSION_FIELD,
+ GENOME_VERSION_37,
+ GENOME_VERSION_38,
+ GENE_SEARCH_FREQUENCIES,
+ GROUPED_VEP_CONSEQUENCES,
+ VEP_GROUP_NONSENSE,
+ VEP_GROUP_ESSENTIAL_SPLICE_SITE,
+ VEP_GROUP_FRAMESHIFT,
+ VEP_GROUP_MISSENSE,
+ VEP_GROUP_INFRAME,
+ VEP_GROUP_SYNONYMOUS,
+ VEP_GROUP_EXTENDED_SPLICE_SITE,
+} from 'shared/utils/constants'
+import { snakecaseToTitlecase } from 'shared/utils/stringUtils'
+import { sendVlmContactEmail } from './reducers'
+import { getVlmDefaultContactEmails, getVlmFamiliesByContactEmail, getSearchedVariantsIsLoading } from './selectors'
-const FIELDS = [
+const LOOKUP_FIELDS = [
{
name: 'variantId',
label: helpLabel('Variant ID', (
@@ -56,6 +72,47 @@ const FIELDS = [
},
]
+const validateAnnotations = (value, { search }) => (
+ value || Object.values(search.annotations || {}).some(val => val.length) ? undefined : 'At least one consequence filter is required'
+)
+
+const CONSEQUENCE_FIELDS = [
+ VEP_GROUP_NONSENSE,
+ VEP_GROUP_ESSENTIAL_SPLICE_SITE,
+ VEP_GROUP_FRAMESHIFT,
+ VEP_GROUP_MISSENSE,
+ VEP_GROUP_INFRAME,
+ VEP_GROUP_SYNONYMOUS,
+ VEP_GROUP_EXTENDED_SPLICE_SITE,
+].map((group, i) => ({
+ name: `search.annotations.${group}`,
+ component: AlignedCheckboxGroup,
+ groupLabel: snakecaseToTitlecase(group),
+ options: GROUPED_VEP_CONSEQUENCES[group],
+ format: value => value || [],
+ inline: true,
+ validate: i === 0 ? validateAnnotations : undefined,
+}))
+
+const GENE_LOOKUP_FIELDS = [
+ { validate: validators.required, ...GENOME_VERSION_FIELD, name: 'allGenomeProjectFamilies' },
+ {
+ name: 'search.locus.rawItems',
+ label: 'Gene',
+ control: AwesomeBarFormInput,
+ categories: ['genes'],
+ fluid: true,
+ placeholder: 'Search for gene',
+ validate: validators.required,
+ },
+ ...CONSEQUENCE_FIELDS,
+]
+
+const INITIAL_GENE_LOOKUP_VALUES = {
+ includeNoAccessProjects: true,
+ search: { freqs: GENE_SEARCH_FREQUENCIES },
+}
+
const VlmDisplay = ({ vlmMatches }) => (
{Object.entries(vlmMatches || {}).map(
@@ -178,12 +235,12 @@ BaseLookupVariant.propTypes = {
vlmDefaultContactEmails: PropTypes.object,
}
-const mapStateToProps = (state, ownProps) => ({
+const mapVariantStateToProps = (state, ownProps) => ({
familiesByContactEmail: getVlmFamiliesByContactEmail(state, ownProps),
vlmDefaultContactEmails: getVlmDefaultContactEmails(state, ownProps),
})
-const LookupVariant = connect(mapStateToProps)(BaseLookupVariant)
+const LookupVariant = connect(mapVariantStateToProps)(BaseLookupVariant)
const VariantDisplay = ({ variants }) => (
(variants || [])[0]?.lookupFamilyGuids ? :
@@ -193,47 +250,37 @@ VariantDisplay.propTypes = {
variants: PropTypes.arrayOf(PropTypes.object),
}
-const onSubmit = updateQueryParams => (data) => {
+const onQueryParamSubmit = (updateQueryParams, data) => {
updateQueryParams(data)
return Promise.resolve()
}
const passThroughResponse = response => response
-const VariantLookup = ({ queryParams, receiveData, updateQueryParams, vlmEnabled }) => (
-
-
-
-
-
-
+const BaseVariantLookupResults = ({ queryParams, receiveData, vlmEnabled }) => ([
+ ...(vlmEnabled ? [(
+
+
+
+
+
+
-
- {vlmEnabled && (
-
-
-
-
-
-
-
-
- )}
- {vlmEnabled && (
-
-
-
-
-
-
- )}
-
+ ), (
+
+
+
+
+
+
+ )] : []), (
+
-
-)
+ ),
+])
-VariantLookup.propTypes = {
+BaseVariantLookupResults.propTypes = {
receiveData: PropTypes.func,
- updateQueryParams: PropTypes.func,
queryParams: PropTypes.object,
vlmEnabled: PropTypes.bool,
}
-const mapGlobalStateToProps = state => ({
+const mapResultsStateToProps = state => ({
vlmEnabled: getVlmEnabled(state),
})
-const mapDispatchToProps = dispatch => ({
+const mapResultsDispatchToProps = dispatch => ({
receiveData: (updatesById) => {
dispatch({ type: RECEIVE_DATA, updatesById })
return updatesById
},
})
-const WrappedVariantLookup = ({ vlmEnabled, ...props }) => (
-
-
-
+const VariantLookupResults = connect(mapResultsStateToProps, mapResultsDispatchToProps)(BaseVariantLookupResults)
+
+const LOOKUP_HEADER = { content: 'Lookup Variant' }
+const GENE_LOOKUP_HEADER = {
+ content: 'Lookup Variants in Gene',
+ subheader: (
+
+ Lookup up all rare variants in seqr in a given gene, regardless of whether or not they are in your
+ projects.
+
+ Variants are only returned if they have a gnomAD Allele Frequency < 3%
+ and have a seqr global Allele Count < 3000.
+
+ For more granular control over the returned variants, we recommend using the main Search page instead of this
+ lookup
+ }
+ content={
+ 'On the Search page, select your desired genome build in the "Include All Projects" section, select the ' +
+ '"Include External Projects" checkbox next to it, and enter your gene of interest in the "Location" search ' +
+ 'box along with your other desired search filters.'
+ }
+ hoverable
+ />
+
+
+
+
+
+ ),
+}
+
+const BaseVariantLookup = ({ match, formProps, onSubmit, ...props }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!match.params.gene && }
+
)
-WrappedVariantLookup.propTypes = {
- vlmEnabled: PropTypes.bool,
+BaseVariantLookup.propTypes = {
+ match: PropTypes.object,
+ formProps: PropTypes.object,
+ onSubmit: PropTypes.func,
}
-export default connect(mapGlobalStateToProps, mapDispatchToProps)(WrappedVariantLookup)
+const mapStateToProps = (state, ownProps) => ({
+ formProps: ownProps.match.params.gene ? {
+ showErrorPanel: true,
+ loading: getSearchedVariantsIsLoading(state),
+ initialValues: INITIAL_GENE_LOOKUP_VALUES,
+ fields: GENE_LOOKUP_FIELDS,
+ } : {
+ fields: LOOKUP_FIELDS,
+ initialValues: ownProps.queryParams,
+ },
+})
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ onSubmit: data => (ownProps.match.params.gene ?
+ dispatch(navigateSavedHashedSearch(data)) :
+ onQueryParamSubmit(ownProps.updateQueryParams, data)
+ ),
+})
+
+const VariantLookup = connect(mapStateToProps, mapDispatchToProps)(BaseVariantLookup)
+
+export default props => (
+
+
+
+)
diff --git a/ui/pages/Search/components/filters/ProjectFamiliesFilter.jsx b/ui/pages/Search/components/filters/ProjectFamiliesFilter.jsx
index fce85b7b04..8790eaa5ee 100644
--- a/ui/pages/Search/components/filters/ProjectFamiliesFilter.jsx
+++ b/ui/pages/Search/components/filters/ProjectFamiliesFilter.jsx
@@ -6,10 +6,11 @@ import { Form, Button } from 'semantic-ui-react'
import {
getProjectsByGuid,
getProjectAnalysisGroupFamilyGuidsByGuid,
+ getProjectDatasetTypes,
} from 'redux/selectors'
import { Multiselect, ButtonRadioGroup } from 'shared/components/form/Inputs'
import { SOLVED_FAMILY_STATUS_OPTIONS } from 'shared/utils/constants'
-import { getProjectFamilies, getSearchContextIsLoading, getFamilyOptions, getAnalysisGroupOptions, getProjectDatasetTypes } from '../../selectors'
+import { getProjectFamilies, getSearchContextIsLoading, getFamilyOptions, getAnalysisGroupOptions } from '../../selectors'
import { loadProjectFamiliesContext } from '../../reducers'
import { ProjectFilter } from './ProjectsField'
diff --git a/ui/pages/Search/reducers.js b/ui/pages/Search/reducers.js
index 8d3e38a32b..67cbd5ea9a 100644
--- a/ui/pages/Search/reducers.js
+++ b/ui/pages/Search/reducers.js
@@ -219,6 +219,10 @@ export const loadGeneBreakdown = searchHash => (dispatch, getState) => {
}
}
+export const sendVlmContactEmail = values => () => new HttpRequestHelper(
+ '/api/send_vlm_email',
+).post(values)
+
// reducers
export const reducers = {
diff --git a/ui/pages/Search/selectors.js b/ui/pages/Search/selectors.js
index 6b49def59a..6fea91ec0d 100644
--- a/ui/pages/Search/selectors.js
+++ b/ui/pages/Search/selectors.js
@@ -12,10 +12,11 @@ import {
getSearchFamiliesByHash,
getGenesById,
getSearchesByHash,
- getSamplesGroupedByProjectGuid,
- getSamplesByFamily,
+ getProjectDatasetTypes,
+ getActiveDatasetsByFamily,
+ getSortedIndividualsByFamily,
} from 'redux/selectors'
-import { FAMILY_ANALYSIS_STATUS_LOOKUP } from 'shared/utils/constants'
+import { getVariantMainGeneId, getVariantSummary, FAMILY_ANALYSIS_STATUS_LOOKUP } from 'shared/utils/constants'
import { compareObjects } from 'shared/utils/sortUtils'
import { compHetGene } from 'shared/components/panel/variants/VariantUtils'
@@ -196,28 +197,15 @@ export const getLocusListOptions = createListEqualSelector(
},
)
-const getSampleDatasetTypes = samples => ([
- ...new Set((samples || []).filter(({ isActive }) => isActive).map(({ datasetType }) => datasetType)),
-])
-
-export const getProjectDatasetTypes = createSelector(
- getProjectsByGuid,
- getSamplesGroupedByProjectGuid,
- (projectsByGuid, samplesByProjectGuid) => Object.values(projectsByGuid).reduce(
- (acc, { projectGuid, datasetTypes }) => ({
- ...acc,
- [projectGuid]: datasetTypes || getSampleDatasetTypes(Object.values(samplesByProjectGuid[projectGuid] || {})),
- }), {},
- ),
-)
-
export const getDatasetTypes = createSelector(
(state, props) => props.projectFamilies,
getProjectDatasetTypes,
- getSamplesByFamily,
- (projectFamilies, projectDatasetTypes, samplesByFamily) => {
+ getActiveDatasetsByFamily,
+ (projectFamilies, projectDatasetTypes, datasetsByFamily) => {
const isSingleFamily = (projectFamilies || []).length === 1 && projectFamilies[0].familyGuids?.length === 1
- const datasetTypes = isSingleFamily ? getSampleDatasetTypes(samplesByFamily[projectFamilies[0].familyGuids[0]]) : (
+ const datasetTypes = isSingleFamily ? [
+ ...new Set((datasetsByFamily[projectFamilies[0].familyGuids[0]] || []).map(({ datasetType }) => datasetType)),
+ ] : (
projectFamilies || []
).reduce((acc, { projectGuid }) => new Set([
...acc, ...(projectDatasetTypes[projectGuid] || [])]), new Set())
@@ -291,3 +279,32 @@ export const getSearchGeneBreakdownValues = createSelector(
}),
})),
)
+
+export const getVlmFamiliesByContactEmail = createSelector(
+ getSortedIndividualsByFamily,
+ (state, ownProps) => ownProps.variant,
+ (individualsByFamily, variant) => (variant.lookupFamilyGuids || []).reduce((acc, familyGuid) => {
+ const individual = individualsByFamily[familyGuid]?.[0]
+ const contactEmail = individual?.projectGuid ? 'internal' : (individual?.vlmContactEmail || 'disabled')
+ return { ...acc, [contactEmail]: [...(acc[contactEmail] || []), familyGuid] }
+ }, {}),
+)
+
+export const getVlmDefaultContactEmails = createSelector(
+ getVlmFamiliesByContactEmail,
+ getGenesById,
+ getUser,
+ (state, ownProps) => ownProps.variant,
+ (familiesByContactEmail, genesById, user, variant) => {
+ const gene = genesById[getVariantMainGeneId(variant)]?.geneSymbol
+ const subject = `${gene || variant.variantId} variant match in seqr`
+ const defaultEmailContent = `harboring ${getVariantSummary(variant)} in ${gene || 'no genes'} (${window.location.href}).
+ \n\nWe have identified the variant in a case with [replace with phenotype].
+ \n\n[List your specific questions for the researcher here.]
+ \n\nWe appreciate your assistance and look forward to hearing more from you.\n\nBest wishes,\n${user.displayName}`
+ return Object.entries(familiesByContactEmail).reduce((acc, [to, familyGuids]) => ({
+ ...acc,
+ [to]: { to, subject, body: `Dear researcher,\n\nWe are interested in learning more about your ${familyGuids.length} cases in seqr ${defaultEmailContent}` },
+ }), {})
+ },
+)
diff --git a/ui/pages/Search/selectors.test.js b/ui/pages/Search/selectors.test.js
index 78b77f1bc7..b8dd139d22 100644
--- a/ui/pages/Search/selectors.test.js
+++ b/ui/pages/Search/selectors.test.js
@@ -64,7 +64,7 @@ test('getLocusListOptions', () => {
test('getDatasetTypes', () => {
expect(getDatasetTypes(STATE, { projectFamilies: [{ projectGuid: PROJECT_GUID }] })).toEqual('SNV_INDEL,SV')
- expect(getDatasetTypes(STATE, EXPECTED_INITAL_SEARCH)).toEqual('SNV_INDEL')
+ expect(getDatasetTypes(STATE, EXPECTED_INITAL_SEARCH)).toEqual('SNV_INDEL,SV')
})
test('getSearchGeneBreakdownValues', () => {
diff --git a/ui/pages/SummaryData/SummaryData.jsx b/ui/pages/SummaryData/SummaryData.jsx
index e683abcb51..01bc14758d 100644
--- a/ui/pages/SummaryData/SummaryData.jsx
+++ b/ui/pages/SummaryData/SummaryData.jsx
@@ -16,7 +16,6 @@ import LocusLists from './components/LocusLists'
import ExternalAnalysis from './components/ExternalAnalysis'
import Hpo from './components/Hpo'
import IndividualMetadata from './components/IndividualMetadata'
-import VariantLookup from './components/VariantLookup'
const GenePage = ({ match }) => (
match.params.geneId ? :
@@ -27,7 +26,6 @@ GenePage.propTypes = {
}
const SUMMARY_DATA_PAGES = [
- { path: 'variant_lookup', component: VariantLookup },
{ path: 'gene_info', params: '/:geneId?', component: GenePage },
{ path: 'gene_lists', component: LocusLists },
{ path: 'saved_variants', component: SavedVariants },
diff --git a/ui/pages/SummaryData/components/IndividualMetadata.test.js b/ui/pages/SummaryData/components/IndividualMetadata.test.js
index 1166facee6..c7a4be4264 100644
--- a/ui/pages/SummaryData/components/IndividualMetadata.test.js
+++ b/ui/pages/SummaryData/components/IndividualMetadata.test.js
@@ -76,8 +76,6 @@ const DATA = [
'phenotype_contribution-2': 'Full',
'partial_contribution_explained-1': 'HP:0000501|HP:0000365',
'partial_contribution_explained-2': '',
- 'ClinGen_allele_ID-1': 'CA1501729',
- 'ClinGen_allele_ID-2': null,
},
]
@@ -93,19 +91,19 @@ test('IndividualMetadata render and export', () => {
'filter_flags', 'consanguinity', 'family_history', 'genetic_findings_id-1', 'variant_reference_assembly-1',
'chrom-1', 'pos-1', 'chrom_end-1', 'pos_end-1', 'ref-1', 'alt-1', 'gene_of_interest-1', 'gene_id-1', 'seqr_chosen_consequence-1', 'transcript-1',
'hgvsc-1', 'hgvsp-1', 'zygosity-1', 'copy_number-1', 'sv_name-1', 'validated_name-1', 'sv_type-1', 'variant_inheritance-1', 'gene_known_for_phenotype-1',
- 'phenotype_contribution-1', 'partial_contribution_explained-1', 'notes-1', 'ClinGen_allele_ID-1',
+ 'phenotype_contribution-1', 'partial_contribution_explained-1', 'notes-1',
'genetic_findings_id-2', 'variant_reference_assembly-2', 'chrom-2', 'pos-2', 'chrom_end-2', 'pos_end-2',
'ref-2', 'alt-2', 'gene_of_interest-2', 'gene_id-2', 'seqr_chosen_consequence-2', 'transcript-2', 'hgvsc-2', 'hgvsp-2',
'zygosity-2', 'copy_number-2', 'sv_name-2', 'validated_name-2', 'sv_type-2', 'variant_inheritance-2', 'gene_known_for_phenotype-2',
- 'phenotype_contribution-2', 'partial_contribution_explained-2', 'notes-2', 'ClinGen_allele_ID-2'])
+ 'phenotype_contribution-2', 'partial_contribution_explained-2', 'notes-2'])
expect(exportConfig.processRow(DATA[0])).toEqual([
'Test Reprocessed Project', 'R0003_test', '12', 'F000012_12', 'NA20889', 'I000017_na20889', '', '', '', '',
'Self', 'Female (XXX)', 'Ashkenazi Jewish', 'Affected', 'HP:0011675 (Arrhythmia)|HP:0001509 ()', '', 'Yes', null,
'OMIM:616126', 'Immunodeficiency 38', 'Autosomal recessive', null, null, undefined, 'Waiting for data', 'Tier 1',
'WES', '2017-02-05', '', undefined, 'Yes', 'NA20889_1_248367227', undefined, '1', 248367227, null, null, 'TC', 'T',
'OR4G11P', 'ENSG00000240361', 'intron_variant', 'ENST00000505820', 'c.3955G>A', 'c.1586-17C>G', 'Heterozygous', null, undefined, undefined, undefined,
- 'unknown', 'Candidate', 'Partial', 'HP:0000501|HP:0000365', undefined, 'CA1501729', 'NA20889_1_249045487_DEL', undefined,
+ 'unknown', 'Candidate', 'Partial', 'HP:0000501|HP:0000365', undefined, 'NA20889_1_249045487_DEL', undefined,
'12', '49045487', null, '49045898', undefined, undefined, undefined, undefined, undefined,
undefined, undefined, undefined, 'Heterozygous', 1, 'DEL:chr12:49045487-49045898', 'DEL:chr12:49045123-49045456', 'Deletion',
- 'unknown', 'Candidate', 'Full', '', undefined, null])
+ 'unknown', 'Candidate', 'Full', '', undefined])
})
diff --git a/ui/pages/SummaryData/reducers.js b/ui/pages/SummaryData/reducers.js
index a3111e8086..761f79b87e 100644
--- a/ui/pages/SummaryData/reducers.js
+++ b/ui/pages/SummaryData/reducers.js
@@ -78,10 +78,6 @@ export const updateExternalAnalysis = values => dispatch => new HttpRequestHelpe
},
).post(values)
-export const sendVlmContactEmail = values => () => new HttpRequestHelper(
- '/api/summary_data/send_vlm_email',
-).post(values)
-
export const reducers = {
successStoryLoading: loadingReducer(REQUEST_SUCCESS_STORY, RECEIVE_SUCCESS_STORY),
successStoryRows: createSingleValueReducer(RECEIVE_SUCCESS_STORY, []),
diff --git a/ui/pages/SummaryData/selectors.js b/ui/pages/SummaryData/selectors.js
index 236dd98249..9676010112 100644
--- a/ui/pages/SummaryData/selectors.js
+++ b/ui/pages/SummaryData/selectors.js
@@ -1,8 +1,3 @@
-import { createSelector } from 'reselect'
-
-import { getSortedIndividualsByFamily, getGenesById, getUser } from 'redux/selectors'
-import { getVariantMainGeneId, getVariantSummary } from 'shared/utils/constants'
-
export const getSuccessStoryLoading = state => state.successStoryLoading.isLoading
export const getSuccessStoryLoadingError = state => state.successStoryLoading.errorMessage
export const getSuccessStoryRows = state => state.successStoryRows
@@ -11,32 +6,3 @@ export const getMmeLoadingError = state => state.mmeLoading.errorMessage
export const getMmeMetrics = state => state.mmeMetrics
export const getMmeSubmissions = state => state.mmeSubmissions
export const getExternalAnalysisUploadStats = state => state.externalAnalysisUploadStats
-
-export const getVlmFamiliesByContactEmail = createSelector(
- getSortedIndividualsByFamily,
- (state, ownProps) => ownProps.variant,
- (individualsByFamily, variant) => (variant.lookupFamilyGuids || []).reduce((acc, familyGuid) => {
- const individual = individualsByFamily[familyGuid]?.[0]
- const contactEmail = individual?.projectGuid ? 'internal' : (individual?.vlmContactEmail || 'disabled')
- return { ...acc, [contactEmail]: [...(acc[contactEmail] || []), familyGuid] }
- }, {}),
-)
-
-export const getVlmDefaultContactEmails = createSelector(
- getVlmFamiliesByContactEmail,
- getGenesById,
- getUser,
- (state, ownProps) => ownProps.variant,
- (familiesByContactEmail, genesById, user, variant) => {
- const gene = genesById[getVariantMainGeneId(variant)]?.geneSymbol
- const subject = `${gene || variant.variantId} variant match in seqr`
- const defaultEmailContent = `harboring ${getVariantSummary(variant)} in ${gene || 'no genes'} (${window.location.href}).
- \n\nWe have identified the variant in a case with [replace with phenotype].
- \n\n[List your specific questions for the researcher here.]
- \n\nWe appreciate your assistance and look forward to hearing more from you.\n\nBest wishes,\n${user.displayName}`
- return Object.entries(familiesByContactEmail).reduce((acc, [to, familyGuids]) => ({
- ...acc,
- [to]: { to, subject, body: `Dear researcher,\n\nWe are interested in learning more about your ${familyGuids.length} cases in seqr ${defaultEmailContent}` },
- }), {})
- },
-)
diff --git a/ui/redux/rootReducer.js b/ui/redux/rootReducer.js
index b93f60411d..d28acefe1f 100644
--- a/ui/redux/rootReducer.js
+++ b/ui/redux/rootReducer.js
@@ -225,12 +225,12 @@ export const updateVariantTags = (values, tagType = 'tags') => {
export const updateVariantClassification = values => updateSavedVariant(values, `${values.variant.variantGuid}/update_acmg_classification`)
-export const updateVariantMainTranscript = (variantGuid, transcriptId) => dispatch => new HttpRequestHelper(
- `/api/saved_variant/${variantGuid}/update_transcript/${transcriptId}`,
+export const updateVariantMainTranscript = (variantGuid, transcript) => dispatch => new HttpRequestHelper(
+ `/api/saved_variant/${variantGuid}/update_transcript/${transcript.transcriptId}`,
(responseJson) => {
dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
},
-).post()
+).post(transcript)
export const updateLocusList = values => (dispatch) => {
let action = 'create'
diff --git a/ui/redux/selectors.js b/ui/redux/selectors.js
index 702baebb9a..3fe2aace45 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
-export const getSamplesByGuid = state => state.samplesByGuid
+const getSamplesByGuid = state => state.samplesByGuid
export const getIgvSamplesByGuid = state => state.igvSamplesByGuid
export const getAnalysisGroupsByGuid = state => state.analysisGroupsByGuid
export const getAnalysisGroupIsLoading = state => state.analysisGroupsLoading.isLoading
@@ -56,7 +56,6 @@ const groupEntitiesByProjectGuid = entities => Object.entries(entities).reduce((
}, {})
export const getFamiliesGroupedByProjectGuid = createSelector(getFamiliesByGuid, groupEntitiesByProjectGuid)
export const getAnalysisGroupsGroupedByProjectGuid = createSelector(getAnalysisGroupsByGuid, groupEntitiesByProjectGuid)
-export const getSamplesGroupedByProjectGuid = createSelector(getSamplesByGuid, groupEntitiesByProjectGuid)
const groupByFamilyGuid = objs => objs.reduce((acc, o) => {
if (!acc[o.familyGuid]) {
@@ -66,6 +65,53 @@ const groupByFamilyGuid = objs => objs.reduce((acc, o) => {
return acc
}, {})
+export const getProjectDatasetTypes = createSelector(
+ getProjectsByGuid,
+ getSamplesByGuid,
+ (projectsByGuid, samplesByGuid) => {
+ 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) {
+ return acc
+ }
+ if (!acc[projectGuid]) {
+ acc[projectGuid] = new Set()
+ }
+ acc[projectGuid].add(datasetType)
+ return acc
+ }, {},
+ )
+ return { ...projectDatasetTypes, ...sampleDatasetTypes }
+ },
+)
+
+export const getDatasetsByIndividual = createSelector(
+ getSamplesByGuid,
+ samplesByGuid => Object.values(samplesByGuid).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] })
+ return acc
+ }, {}),
+)
+
+export const getActiveDatasetsByIndividual = createSelector(
+ getDatasetsByIndividual,
+ datasetsByIndividual => Object.entries(datasetsByIndividual).reduce((acc, [individualGuid, datasets]) => ({
+ ...acc,
+ [individualGuid]: datasets.filter(({ isActive }) => isActive).map(
+ ({ sampleType, datasetType, loadedDate }) => ({ sampleType, datasetType, loadedDate, individualGuid }),
+ ),
+ }), {}),
+)
+
export const getNotesByFamilyType = createSelector(
getFamilyNotesByGuid,
notesByGuid => Object.values(notesByGuid).reduce((acc, note) => {
@@ -131,23 +177,46 @@ export const getSortedIndividualsByFamily = createSelector(
groupByFamilyGuid,
)
-const getSortedSamples = createSelector(
- getSamplesByGuid,
- samplesByGuid => Object.values(samplesByGuid).sort((a, b) => a.loadedDate.localeCompare(b.loadedDate)),
+export const getActiveDatasetsByFamily = createSelector(
+ getFamiliesByGuid,
+ getActiveDatasetsByIndividual,
+ (familiesByGuid, activeDatasetsByIndividual) => Object.entries(familiesByGuid).reduce(
+ (acc, [familyGuid, { individualGuids = [] }]) => ({
+ ...acc,
+ [familyGuid]: individualGuids.reduce(
+ (familyAcc, individualGuid) => [...familyAcc, ...(activeDatasetsByIndividual[individualGuid] || [])], [],
+ ),
+ }),
+ {},
+ ),
)
-export const getSamplesByFamily = createSelector(
- getSortedSamples,
- sortedSamples => groupByFamilyGuid(sortedSamples || []),
+export const getMinMaxDatasetsByFamily = createSelector(
+ getFamiliesByGuid,
+ getDatasetsByIndividual,
+ (familiesByGuid, datasetsByIndividual) => Object.entries(familiesByGuid).reduce(
+ (acc, [familyGuid, { individualGuids }]) => ({
+ ...acc,
+ [familyGuid]: individualGuids.reduce(([minLoaded, maxLoaded], individualGuid) => {
+ const individualDatasets = datasetsByIndividual[individualGuid] || []
+ if (individualDatasets.length === 0) {
+ return [minLoaded, maxLoaded]
+ }
+ const endIndex = individualDatasets.length - 1
+ return [
+ minLoaded?.loadedDate < individualDatasets[0]?.loadedDate ? minLoaded : individualDatasets[0],
+ maxLoaded?.loadedDate > individualDatasets[endIndex]?.loadedDate ? maxLoaded : individualDatasets[endIndex],
+ ]
+ }, [null, null]),
+ }),
+ {},
+ ),
)
export const getHasActiveSearchSampleByFamily = createSelector(
- getSamplesByFamily,
- samplesByFamily => Object.entries(samplesByFamily).reduce(
- (acc, [familyGuid, familySamples]) => ({
- ...acc,
- [familyGuid]: familySamples.some(({ isActive }) => isActive),
- }), {},
+ getActiveDatasetsByFamily,
+ activeDatasetsByFamily => Object.entries(activeDatasetsByFamily).reduce(
+ (acc, [familyGuid, familyDatasets]) => ({ ...acc, [familyGuid]: familyDatasets.length > 0 }), {},
),
)
@@ -428,8 +497,8 @@ const isAnalysedBy = (family, analysedByFilter, user, analysedByOptions) => {
export const familyPassesFilters = createSelector(
getUser,
- getSamplesByFamily,
- (user, samplesByFamily) => (
+ getActiveDatasetsByFamily,
+ (user, activeDatasetsByFamily) => (
family, groupedFilters, analysedByOptions, categoryFilters = CATEGORY_FAMILY_FILTERS,
) => {
if (groupedFilters.analysedBy && !isAnalysedBy(family, groupedFilters.analysedBy, user, analysedByOptions)) {
@@ -439,7 +508,7 @@ export const familyPassesFilters = createSelector(
const filters = categoryFilters[key]?.filter(
opt => groupVals.includes(opt.value) && opt.createFilter,
).map(opt => opt.createFilter)
- return !filters?.length || filters.some(filter => filter(family, user, samplesByFamily))
+ return !filters?.length || filters.some(filter => filter(family, user, activeDatasetsByFamily))
})
},
)
diff --git a/ui/shared/components/page/Header.jsx b/ui/shared/components/page/Header.jsx
index 1afbc17906..f56839d732 100644
--- a/ui/shared/components/page/Header.jsx
+++ b/ui/shared/components/page/Header.jsx
@@ -26,6 +26,8 @@ const PageHeader = React.memo(({ user, oauthLoginProvider, onSubmit }) => {
{Object.keys(user).length ? [
,
+ ,
+ ,
,
(user.isAnalyst || user.isPm) ? : null,
(user.isDataManager || user.isPm) ? : null,
diff --git a/ui/shared/components/page/PageHeader.jsx b/ui/shared/components/page/PageHeader.jsx
index 269b2ebd14..5cb7d96fd5 100644
--- a/ui/shared/components/page/PageHeader.jsx
+++ b/ui/shared/components/page/PageHeader.jsx
@@ -33,6 +33,7 @@ export const GenePageHeader = connect(mapGeneStateToProps)(BaseGenePageHeader)
const NO_HEADER_PAGES = [
'/dashboard', '/create_project_from_workspace', '/workspace', '/users', '/login', '/accept_policies', ...PUBLIC_PAGES,
+ '/variant_lookup',
]
const NO_HEADER_PAGE_TITLES = {
diff --git a/ui/shared/components/panel/family/FamilyFields.jsx b/ui/shared/components/panel/family/FamilyFields.jsx
index 2f3834dbde..a2a529f0c2 100644
--- a/ui/shared/components/panel/family/FamilyFields.jsx
+++ b/ui/shared/components/panel/family/FamilyFields.jsx
@@ -7,7 +7,7 @@ import styled from 'styled-components'
import { loadUserOptions, updateFamily } from 'redux/rootReducer'
import { loadProjectAnalysisGroups } from 'redux/utils/reducerUtils'
import {
- getSamplesByFamily,
+ getMinMaxDatasetsByFamily,
getUserOptionsIsLoading,
getHasActiveSearchSampleByFamily,
getUserOptions,
@@ -28,22 +28,22 @@ const NoWrap = styled.div`
white-space: nowrap;
`
-const BaseFirstSample = React.memo(({ firstFamilySample, compact, hasActiveVariantSample }) => (
+const BaseFirstSample = React.memo(({ firstFamilyDataset, compact, hasActiveVariantSample }) => (
))
BaseFirstSample.propTypes = {
- firstFamilySample: PropTypes.object,
+ firstFamilyDataset: PropTypes.object,
compact: PropTypes.bool,
hasActiveVariantSample: PropTypes.bool,
}
const mapSampleDispatchToProps = (state, ownProps) => ({
- firstFamilySample: (getSamplesByFamily(state)[ownProps.familyGuid] || [])[0],
+ firstFamilyDataset: (getMinMaxDatasetsByFamily(state)[ownProps.familyGuid] || [])[0],
hasActiveVariantSample: getHasActiveSearchSampleByFamily(state)[ownProps.familyGuid],
})
diff --git a/ui/shared/components/panel/variants/Annotations.jsx b/ui/shared/components/panel/variants/Annotations.jsx
index 6d0a58613a..fbb99e52fd 100644
--- a/ui/shared/components/panel/variants/Annotations.jsx
+++ b/ui/shared/components/panel/variants/Annotations.jsx
@@ -352,7 +352,7 @@ const variantSearchLinks = (variant, mainTranscript, genesById, user) => {
const seqrSearchLink = (
Variant Lookup
diff --git a/ui/shared/components/panel/variants/Transcripts.jsx b/ui/shared/components/panel/variants/Transcripts.jsx
index 5406ebbe5b..c049d9dae8 100644
--- a/ui/shared/components/panel/variants/Transcripts.jsx
+++ b/ui/shared/components/panel/variants/Transcripts.jsx
@@ -143,7 +143,7 @@ const transcriptIdDetails = (transcript, variant, { transcriptsById, project, up
transcript.transcriptId === variant.selectedMainTranscriptId ?
: (
@@ -223,8 +223,8 @@ const mapStateToProps = (state, ownProps) => ({
})
const mapDispatchToProps = (dispatch, ownProps) => ({
- updateMainTranscript: transcriptId => () => (
- dispatch(updateVariantMainTranscript(ownProps.variant.variantGuid, transcriptId))
+ updateMainTranscript: transcript => () => (
+ dispatch(updateVariantMainTranscript(ownProps.variant.variantGuid, transcript))
),
})
diff --git a/ui/shared/utils/constants.js b/ui/shared/utils/constants.js
index b91aeac223..fa5fd1b117 100644
--- a/ui/shared/utils/constants.js
+++ b/ui/shared/utils/constants.js
@@ -390,9 +390,9 @@ const SHOW_ANALYSED_BY_ME = 'SHOW_ANALYSED_BY_ME'
const SHOW_ANALYSED = 'SHOW_ANALYSED'
const SHOW_NOT_ANALYSED = 'SHOW_NOT_ANALYSED'
-const hasMatchingSampleFilter = isMatchingSample => (family, user, samplesByFamily) => (
- (family.sampleTypes || samplesByFamily[family.familyGuid] || []).some(
- sample => sample.isActive && isMatchingSample(sample),
+const hasMatchingDatasetFilter = isMatchingDataset => (family, user, activeDatasetsByFamily) => (
+ (family.sampleTypes || activeDatasetsByFamily[family.familyGuid] || []).some(
+ sample => isMatchingDataset(sample),
))
export const ASSIGNED_TO_ME_FILTER = {
@@ -447,7 +447,7 @@ export const CATEGORY_FAMILY_FILTERS = {
{
value: SHOW_DATA_LOADED,
name: 'Data Loaded',
- createFilter: hasMatchingSampleFilter(() => true),
+ createFilter: hasMatchingDatasetFilter(() => true),
},
{
value: `${SHOW_DATA_LOADED}_RNA`,
@@ -457,7 +457,7 @@ export const CATEGORY_FAMILY_FILTERS = {
...[DATASET_TYPE_SV_CALLS, DATASET_TYPE_MITO_CALLS].map(dataType => ({
value: `${SHOW_DATA_LOADED}_${dataType}`,
name: `Data Loaded -${DATASET_TITLE_LOOKUP[dataType]}`,
- createFilter: hasMatchingSampleFilter(
+ createFilter: hasMatchingDatasetFilter(
({ datasetType }) => datasetType === dataType,
),
})),
@@ -2089,7 +2089,6 @@ export const VARIANT_METADATA_COLUMNS = [
{ name: 'phenotype_contribution' },
{ name: 'partial_contribution_explained' },
{ name: 'notes' },
- { name: 'ClinGen_allele_ID' },
]
export const BASE_FAMILY_METADATA_COLUMNS = [