diff --git a/src/encoded/schemas/variant_sample.json b/src/encoded/schemas/variant_sample.json index 47021620e6..030a94628c 100644 --- a/src/encoded/schemas/variant_sample.json +++ b/src/encoded/schemas/variant_sample.json @@ -509,31 +509,41 @@ }, "variant.genes.genes_ensg.display_title": { "title": "Gene Transcript", - "order": 25 + "order": 25, + "sort_fields": [ + { "field": "variant.genes.genes_ensg.display_title", "title": "Gene" }, + { "field": "variant.genes.genes_most_severe_transcript", "title": "Most Severe Transcript" } + ] }, "variant.genes.genes_most_severe_hgvsc": { "title": "Variant", - "order": 27, - "sort_fields": [ - { "field": "variant.genes.genes.most_severe_hgvsc", "title": "Most Severe HGVSC" } - ] + "order": 27 }, "variant.genes.genes_most_severe_consequence.coding_effect": { "title": "Coding Effect", "order": 28, "sort_fields": [ - { "fields": "variant.genes.genes_most_severe_consequence.coding_effect", "title": "Coding Effect" } + { "field": "variant.genes.genes_most_severe_consequence.coding_effect", "title": "Coding Effect" } ] }, "variant.gnomad_af": { "title": "GnomAD", "aggregation_type": "stats", "number_step": "any", - "order": 60 + "order": 60, + "sort_fields": [ + { "field": "variant.gnomad_af", "title": "Gnomad AF" }, + { "field": "variant.max_pop_af_af_popmax", "title": "Gnomad AF Population Max" } + ] }, "variant.cadd_phred": { "title": "Predictors", - "order": 61 + "order": 61, + "sort_fields": [ + { "field": "variant.cadd_phred", "title": "Cadd Phred Score" }, + { "field": "variant.spliceai_maxds", "title": "SpliceAI Max DS"}, + { "field": "variant.conservation_phylop100", "title": "PhyloP 100 Score"} + ] }, "bam_snapshot": { "title": "Genome Snapshot", diff --git a/src/encoded/static/components/browse/columnExtensionMap.js b/src/encoded/static/components/browse/columnExtensionMap.js index 46eca6e197..c617343e8c 100644 --- a/src/encoded/static/components/browse/columnExtensionMap.js +++ b/src/encoded/static/components/browse/columnExtensionMap.js @@ -315,7 +315,7 @@ export const columnExtensionMap = { /** "Bioinformatics" column title */ 'sample_processing.analysis_type': { 'render' : function renderBioinformaticsColumn(result, parentProps){ - const { '@id' : resultHrefPath, sample = null, sample_processing = null } = result; + const { '@id' : resultHrefPath, sample = null, sample_processing = null, vcf_file = null } = result; if (!sample_processing) return null; // Unsure if possible, but fallback to null / '-' in case so (not showing datetitle etc) const { analysis_type: mainTitle = null, @@ -323,7 +323,7 @@ export const columnExtensionMap = { } = sample_processing; const { files = [], - processed_files = [] + // processed_files = [] } = sample || {}; let status = null; @@ -343,23 +343,17 @@ export const columnExtensionMap = { } } - // If fastQs are present... + // If fastQs are present... check if ingested if (status === "in progress") { - // Check if VCFs have been ingested & if QCs passed - for (let j = 0; j < processed_files.length; j++) { - const procFile = processed_files[j]; - const { file_ingestion_status: ingestionStatus = null, file_format = null } = procFile || {}; - const { file_format: fileType } = file_format || {}; - if ((fileType === "vcf_gz" || fileType === "gvcf_gz") - && ingestionStatus === "Ingested") { - statusTip = "Variants are ingested"; - status = "complete"; - - // TODO: Add QC status from overall QC status once implemented - } // Else, not VCF or not yet ingested, ignore + const { file_ingestion_status: ingestionStatus = null } = vcf_file || {}; + if (ingestionStatus && ingestionStatus === "Ingested") { + statusTip = "Variants are ingested"; + status = "complete"; } } + // Check if overall QCs passed (not yet implemented) -- to add later + // Unlikely to show in non-Case item results, so didn't add Case filter return ( @@ -467,11 +461,12 @@ export const columnExtensionMap = { } }, 'bam_snapshot': { + 'noSort' : true, 'render' : function(result, props) { const { bam_snapshot = null, uuid = null } = result; if (bam_snapshot) { return ( -
+
View BAM Snapshot diff --git a/src/encoded/static/components/item-pages/CaseView/FilteringTab.js b/src/encoded/static/components/item-pages/CaseView/FilteringTab.js index 93f4f9a891..2cabb79ea7 100644 --- a/src/encoded/static/components/item-pages/CaseView/FilteringTab.js +++ b/src/encoded/static/components/item-pages/CaseView/FilteringTab.js @@ -18,8 +18,8 @@ const GenesMostSevereHGVSCColumn = React.memo(function GenesMostSevereHGVSCColum const hgvscSplit = hgvsc.split(":"); const pSplit = hgvscSplit[1].split("."); // Will add hgvsp when added in data/backend - const rows = [
{ pSplit[0] }.{ pSplit[1] }
]; - return ; + const rows = [
{ pSplit[0] }.{ pSplit[1] }
]; + return ; }); function CaseViewEmbeddedVariantSampleSearchTable(props){ @@ -40,6 +40,7 @@ function CaseViewEmbeddedVariantSampleSearchTable(props){ ...originalColExtMap, // Copy in existing vals but overwrite display_title.render "display_title" : { ...originalColExtMap.display_title, + widthMap: { 'lg' : 250, 'md' : 220, 'sm' : 200 }, render: function(result, parentProps){ const { href, context, rowNumber, detailOpen, toggleDetailOpen } = parentProps; return ( @@ -51,50 +52,58 @@ function CaseViewEmbeddedVariantSampleSearchTable(props){ }, // TODO? We could potentially move some of these definitions into EmbeddedItemSearchTable.defaultProps.columnExtensionMap "DP" : { // Coverage, VAF column + widthMap: { 'lg' : 140, 'md' : 120, 'sm' : 70 }, render: function(result, props) { const { DP = null, AF = null } = result; const rows = [ - {DP || "-"}, - {AF || "-"} + {DP || "-"}, + {AF || "-"} ]; - return ; + return ; } }, "associated_genotype_labels.proband_genotype_label" : { // Genotype column - widthMap: { 'lg' : 280, 'md' : 250, 'sm' : 200 }, + widthMap: { 'lg' : 240, 'md' : 230, 'sm' : 200 }, render: function(result, props) { const { associated_genotype_labels : { proband_genotype_label = null, mother_genotype_label = null, father_genotype_label = null } = {} } = result; const rows = []; if (proband_genotype_label) { - rows.push(
Proband: {proband_genotype_label}
); + rows.push(
Proband: {proband_genotype_label}
); } else { return null; } if (mother_genotype_label) { - rows.push(
Mother: {mother_genotype_label || "-"}
); + rows.push(
Mother: {mother_genotype_label || "-"}
); } if (father_genotype_label) { - rows.push(
Father: {father_genotype_label || "-"}
); + rows.push(
Father: {father_genotype_label || "-"}
); } - return ; + return ; } }, "variant.genes.genes_ensg.display_title": { // Gene Transcript column + widthMap: { 'lg' : 155, 'md' : 140, 'sm' : 130 }, render: function(result, props) { - const { variant : { genes : [firstGene = null] = [] } = {} } = result; - const { genes_ensg: { display_title = null } = {}, genes_most_severe_transcript = null } = firstGene || {}; + const { variant : { genes = [] } = {} } = result; - if (firstGene) { + const geneTitles = genes.map((geneItem) => { + const { genes_ensg: { display_title = null } = {} } = geneItem || {}; + return display_title; + }); + if (genes.length > 0) { + const { genes_most_severe_transcript = null } = genes[0] || {}; const rows = [ - { display_title} , - { genes_most_severe_transcript} + { geneTitles.length > 1 ? geneTitles.join() : geneTitles } , + { genes_most_severe_transcript} ]; - return ; + return ; } return null; } }, "variant.genes.genes_most_severe_hgvsc": { // Variant column + noSort: true, + widthMap: { 'lg' : 120, 'md' : 110, 'sm' : 95 }, render: function(result, props) { const { variant : { genes : [firstGene = null] = [] } = {} } = result; const { genes_most_severe_hgvsc = null } = firstGene || {}; @@ -106,17 +115,19 @@ function CaseViewEmbeddedVariantSampleSearchTable(props){ } }, "variant.genes.genes_most_severe_consequence.coding_effect": { // Coding Effect column + widthMap: { 'lg' : 140, 'md' : 130, 'sm' : 120 }, render: function(result, props) { const { variant : { genes : [firstGene = null] = [] } = {} } = result; const { genes_most_severe_consequence : { coding_effect = null } = {} } = firstGene || {}; if (firstGene && coding_effect) { - return ; + return ; } return null; } }, "variant.gnomad_af": { // Gnomad column + widthMap: { 'lg' : 140, 'md' : 130, 'sm' : 120 }, render: function(result, props){ const { variant : { gnomad_af = null, max_pop_af_af_popmax = null } = {} } = result; const rows = []; @@ -125,12 +136,14 @@ function CaseViewEmbeddedVariantSampleSearchTable(props){ return null; } if (gnomad_af) { - rows.push(
ALL: {gnomad_af || "-"}
); + const gnomad_af_exp = gnomad_af ? gnomad_af.toExponential(3): null; + rows.push(
ALL: {gnomad_af_exp || gnomad_af || "-"}
); } if (max_pop_af_af_popmax){ - rows.push(
MAX: {max_pop_af_af_popmax || "-"}
); + const max_pop_af_af_popmax_exp = max_pop_af_af_popmax ? max_pop_af_af_popmax.toExponential(3): null; + rows.push(
MAX: {max_pop_af_af_popmax_exp || max_pop_af_af_popmax || "-"}
); } - return ; + return ; } }, "variant.cadd_phred": { // Predictors column (cadd_phred, spliceai, phylop100) @@ -142,15 +155,15 @@ function CaseViewEmbeddedVariantSampleSearchTable(props){ return null; } if (cadd_phred) { - rows.push(
Cadd Phred: {cadd_phred || "-"}
); + rows.push(
Cadd Phred: {cadd_phred || "-"}
); } if (spliceai_maxds) { - rows.push(
SpliceAI MaxDS: {spliceai_maxds || "-"}
); + rows.push(
SpliceAI MaxDS: {spliceai_maxds || "-"}
); } if (conservation_phylop100) { - rows.push(
PhyloP 100: {conservation_phylop100 || "-"}
); + rows.push(
PhyloP 100: {conservation_phylop100 || "-"}
); } - return ; + return ; } } }; @@ -160,7 +173,7 @@ function CaseViewEmbeddedVariantSampleSearchTable(props){ function StackedRowColumn(props) { const { rowKey = null, rows = [], className = null } = props; - const cls = ("w-100 d-flex flex-column text-ellipsis-container" + (className ? " " + className : "")); + const cls = ("w-100" + (className ? " " + className : "")); return (
{ rows } @@ -177,9 +190,9 @@ const VSDisplayTitleColumnDefault = React.memo(function VSDisplayTitleColumnDefa const { variant = null } = result || {}; const { display_title = null, dbsnp_rs_number = null } = variant; - const cls = ("title-block text-ellipsis-container" + (className ? " " + className : "")); + const cls = ("title-block" + (className ? " " + className : "")); const rows = [ - {display_title} + {display_title} ]; if (dbsnp_rs_number) { @@ -187,7 +200,7 @@ const VSDisplayTitleColumnDefault = React.memo(function VSDisplayTitleColumnDefa } return ( - + ); diff --git a/src/encoded/static/components/item-pages/CaseView/index.js b/src/encoded/static/components/item-pages/CaseView/index.js index a7383df329..5c1624f424 100644 --- a/src/encoded/static/components/item-pages/CaseView/index.js +++ b/src/encoded/static/components/item-pages/CaseView/index.js @@ -7,6 +7,7 @@ import url from 'url'; import { console, layout, navigate, ajax } from '@hms-dbmi-bgm/shared-portal-components/es/components/util'; import { PartialList } from '@hms-dbmi-bgm/shared-portal-components/es/components/ui/PartialList'; +import { decorateNumberWithCommas } from '@hms-dbmi-bgm/shared-portal-components/es/components/util/value-transforms'; import { PedigreeVizView } from './../../viz/PedigreeViz'; import DefaultItemView from './../DefaultItemView'; @@ -612,7 +613,7 @@ const BioinfoStats = React.memo(function BioinfoStats(props) { : null }
{/* 4,769,578 */} - { (msaStats.totalVariants && msaStats.totalVariants.value) || "" } + { (msaStats.totalVariants && msaStats.totalVariants.value) ? decorateNumberWithCommas(msaStats.totalVariants.value): "" }
@@ -660,7 +661,7 @@ const BioinfoStats = React.memo(function BioinfoStats(props) { : null }
{/* Ex. 1,273 */} - { (msaStats.filteredVariants && msaStats.filteredVariants.value) || "" } + { (msaStats.filteredVariants && msaStats.filteredVariants.value) ? decorateNumberWithCommas(msaStats.filteredVariants.value) : "" }
diff --git a/src/encoded/static/components/item-pages/VariantSampleView/SampleTabBody.js b/src/encoded/static/components/item-pages/VariantSampleView/SampleTabBody.js index 6c47785671..66940dd295 100644 --- a/src/encoded/static/components/item-pages/VariantSampleView/SampleTabBody.js +++ b/src/encoded/static/components/item-pages/VariantSampleView/SampleTabBody.js @@ -98,6 +98,36 @@ export function SampleTabBody(props){ ); } +/** + * Returns an object with shortened genotypes: one with number of base pairs, one without + * @param {*} fullGT Full genotype (ex. AGAGAATTCCACTCACATCG) + * @param {*} n Number of base pairs to show on either side of the shortening (if "n=2", shortGT would look like "AG...TC") + */ +function shortenGT(fullGT, n) { + let gtObj = {}; + + // Only calculate shortened values if fullGT is longer than 10 characters + if (fullGT.length > 10) { + // Calculate shortened versions of GTs + const firstN = fullGT.substring(0, n); + const middle = fullGT.substring(n, fullGT.length - n); + const lastN = fullGT.substring(fullGT.length - n, fullGT.length); + const shortGT = `${firstN}...${lastN}`; + const bpGT = `${firstN}...${middle.length}bp...${lastN}`; + + // // Determine if to use ...BP... shortened version or normal shortened version + // if (!shortGTDupTester[shortGT]) { + // shortGTDupTester[shortGT] = true; + // } else { + // shortGTDupPresent = true; + // } + + gtObj = { fullGT, shortGT, bpGT }; + } else { + gtObj = { fullGT }; + } + return gtObj; +} function CoverageTableRow(props) { const { @@ -143,6 +173,10 @@ const CoverageTable = React.memo(function CoverageTable({ samplegeno = [], genot const mapGTToShortenedTitles = {}; const shortGTDupTester = {}; + // Add shortened ref to titlemap + const refShortened = shortenGT(varRef, 2); + mapGTToShortenedTitles[varRef] = refShortened; + let samplegenolen; // Populate ID->Coverage map with genotype label information @@ -183,26 +217,14 @@ const CoverageTable = React.memo(function CoverageTable({ samplegeno = [], genot // Populate mapGTToShortenedTitles mapping if (!mapGTToShortenedTitles.hasOwnProperty(fullGT)) { - let gtObj; - const n = 2; // Number of bp to show on either end when shortened - - if (fullGT.length > 10) { // Calculate shortened versions of GTs - const firstN = fullGT.substring(0, n); - const middle = fullGT.substring(n, fullGT.length - n); - const lastN = fullGT.substring(fullGT.length - n, fullGT.length); - const shortGT = `${firstN}...${lastN}`; - const bpGT = `${firstN}...${middle.length}bp...${lastN}`; - - // Determine if to use ...BP... shortened version or normal shortened version - if (!shortGTDupTester[shortGT]) { - shortGTDupTester[shortGT] = true; - } else { - shortGTDupPresent = true; - } - - gtObj = { fullGT, shortGT, bpGT }; + const gtObj = shortenGT(fullGT, 2); + const { shortGT = null } = gtObj; + + // Determine if to use ...BP... shortened version or normal shortened version + if (!shortGTDupTester[shortGT]) { + shortGTDupTester[shortGT] = true; } else { - gtObj = { fullGT }; + shortGTDupPresent = true; } mapGTToShortenedTitles[fullGT] = gtObj; @@ -257,7 +279,8 @@ const CoverageTable = React.memo(function CoverageTable({ samplegeno = [], genot if (!mapNumgtToGT[i]) { // if it's ref, just use ref placeholder to replace if (i === 0) { - refAndAltCols.push(Ref ({varRef})); + const { bpGT = null, shortGT = null } = shortenGT(varRef, 2); + refAndAltCols.push(Ref ({bpGT || shortGT || varRef})); } else { // apply placeholder name to empty alt column refAndAltCols.push(Alt (#{emptyColCount})); emptyColCount++; diff --git a/src/encoded/types/case.py b/src/encoded/types/case.py index b98474973c..b18451933e 100644 --- a/src/encoded/types/case.py +++ b/src/encoded/types/case.py @@ -169,7 +169,6 @@ class Case(Item): "sample.files.status", "sample.completed_processes", "sample.processed_files.file_format.file_format", - "sample.processed_files.file_ingestion_status", "sample.processed_files.quality_metric.quality_metric_summary.sample", "sample.processed_files.quality_metric.quality_metric_summary.title", "sample.processed_files.quality_metric.quality_metric_summary.value", @@ -219,7 +218,8 @@ class Case(Item): "active_filterset.filter_blocks.flags_applied", "active_filterset.flags", "cohort.filter_set.*", - "project.name" + "project.name", + "vcf_file.file_ingestion_status" ] @calculated_property(schema={