diff --git a/main/js/afterSubmit.js b/main/js/afterSubmit.js index f32f5ed3..ad2ae4aa 100644 --- a/main/js/afterSubmit.js +++ b/main/js/afterSubmit.js @@ -37,7 +37,8 @@ const buildPlots = async function() { if (isEmpty(selectedTumorTypes) || isEmpty(allSelectedGenes) ) { console.log("user did not provide enough information for query"); - window.alert("Please select at least one tumor type and gene."); + let message = "Please select at least one tumor type and gene to generate plots."; + handleDataFetchError(message); return null; } @@ -86,7 +87,8 @@ const buildPlots = async function() { } else { //Set screen text to indicate that no plots were generated due to lack of patient barcodes - handleEmptyCohort(); + message = "The gene mutation and/or metadata filter(s) produced a cohort with no patients. To see figures, please change the gene mutation and/or metadata filter(s)."; + handleDataFetchError(message); return null; } expressionData = (expressionData || []).filter( @@ -533,14 +535,15 @@ let downloadClinicalData = function(cohortID, clinicalData, barcodes_clin) { } } -let handleEmptyCohort = function () { - let empty_cohort_message = "The gene mutation and/or metadata filter(s) produced a cohort with no patients. To see figures, please change the gene mutation and/or metadata filter(s)." +let handleDataFetchError = function (message) { // Remove the loaders from heatmap, violin, and survival tabs document.getElementById("heatmapLoaderDiv").classList.remove("loader"); document.getElementById("violinLoaderDiv").classList.remove("loader"); document.getElementById("survivalLoaderDiv").classList.remove("loader"); // Set text of each plot tab - d3.select("#heatmapLoaderDiv").html(empty_cohort_message); - d3.select("#violinLoaderDiv").html(empty_cohort_message); - d3.select("#survivalLoaderDiv").html(empty_cohort_message); + error_message_font_size = "16px"; + error_message_font_color = "#8B0000"; + d3.select("#heatmapLoaderDiv").html(message).style("color", error_message_font_color).style("font-size", error_message_font_size); + d3.select("#violinLoaderDiv").html(message).style("color", error_message_font_color).style("font-size", error_message_font_size); + d3.select("#survivalLoaderDiv").html(message).style("color", error_message_font_color).style("font-size", error_message_font_size); }; \ No newline at end of file diff --git a/main/js/dataAcquisition/fetch.js b/main/js/dataAcquisition/fetch.js index 12a58098..17e2f864 100644 --- a/main/js/dataAcquisition/fetch.js +++ b/main/js/dataAcquisition/fetch.js @@ -28,7 +28,7 @@ const _fetchFromFireBrowse = async function(endpoint, params, expectedKey) { const json = await response.json(); return json; } catch(error) { - console.log(`${expectedKey} is empty, returning an object with empty ${expectedKey} `); + console.warn(`${expectedKey} is empty, returning an object with empty ${expectedKey} `); return minimalJson; } }; @@ -163,7 +163,11 @@ firebrowse.fetch = async function(endpoint, params, groupBy) { ProgressBar.setPercentage(i/paramsMatrix.length*100, "Fetching " + expectedKey); // Run a fetch and then collect the data into one common object. await _fetchFromFireBrowse(endpoint, paramsForThisCall, expectedKey) - .then(x => {results[expectedKey].push(...x[expectedKey])}); + .then(x => { + // Verify retrieved data has expectedKey as a field + if(expectedKey in x) + results[expectedKey].push(...x[expectedKey]) + }); } ProgressBar.cleanUp(); return results; @@ -205,7 +209,12 @@ firebrowse.fetchClinicalFH = async function({cohorts, genes, barcodes, pageNum}) groupBy.push({key: "tcga_participant_barcode", length: 50}); } const data = await firebrowse.fetch("/Samples/Clinical_FH", params, groupBy); - return data.Clinical_FH; + // Check that firebrowse.fetch() returned properly formatted data + let data_field = "Clinical_FH"; + if(data_field in data) + return data[data_field]; + else + throw new Error("Clinical data could not be fetched from Firebrowse"); }; @@ -219,8 +228,13 @@ firebrowse.fetchClinicalFH = async function({cohorts, genes, barcodes, pageNum}) */ firebrowse.fetchCohorts = async function() { const params = { format: "json" }; + //Check that Firebrowse fetch returned properly formatted data const fetchResponse = await firebrowse.fetch("/Metadata/Cohorts", params); - return fetchResponse.Cohorts; + let data_field = "Cohorts"; + if(data_field in fetchResponse) + return fetchResponse[data_field]; + else + throw new Error("Cohort names could not be fetched from Firebrowse"); }; /** Get the number of mRNASeq samples per cohort. @@ -242,8 +256,12 @@ firebrowse.fetchCounts = async function(cohorts) { data_type: "mrnaseq", totals: "true", }; - const fetchResponse = await firebrowse.fetch("/Metadata/Counts", params); - return fetchResponse.Counts; + const data = await firebrowse.fetch("/Metadata/Counts", params); + let data_field = "Counts" + if(data_field in data) + return data[data_field]; + else + throw new Error("Cohort counts could not be fetched from Firebrowse"); }; firebrowse.fetchMutationMAF = async function ({cohorts, genes}) { @@ -275,7 +293,11 @@ firebrowse.fetchMutationMAF = async function ({cohorts, genes}) { else { data = await firebrowse.fetch("/Analyses/Mutation/MAF", params, groupBy); } - return data.MAF; + let data_field = "MAF" + if(data_field in data) + return data[data_field]; + else + throw new Error("Could not fetch MAF mutation data from Firehose"); }; @@ -324,7 +346,12 @@ firebrowse.fetchmRNASeq = async function({cohorts, genes, barcodes}) { groupBy.push({key: "tcga_participant_barcode", length: 400}); } const data = await firebrowse.fetch("/Samples/mRNASeq", params, groupBy); - return data.mRNASeq; + let data_field = "mRNASeq" + if(data_field in data) + return data[data_field]; + else + throw new Error("Could not fetch mRNASeq expression data from Firehose"); + }; // Prevent any changes to this object. diff --git a/main/js/fillSelectBoxes.js b/main/js/fillSelectBoxes.js index 68f14be2..cf682022 100644 --- a/main/js/fillSelectBoxes.js +++ b/main/js/fillSelectBoxes.js @@ -12,31 +12,35 @@ * @returns {undefined} */ const fillCancerTypeSelectBox = async function () { - const cancerTypesQuery = await firebrowse.fetchCohorts(); - const cancerTypesQueryNoFPPP = cancerTypesQuery.reduce((acc, item) => item.cohort !== 'FPPP' ? [...acc, item] : acc, []); - cancerTypesQueryNoFPPP.sort(); - let selectBox = document.getElementById("cancerTypeMultipleSelection"); - for (let i = 0; i < cancerTypesQueryNoFPPP.length; i++) { - let currentOption = document.createElement("option"); - currentOption.value = cancerTypesQueryNoFPPP[i]["cohort"]; - currentOption.text = - "(" + - cancerTypesQueryNoFPPP[i]["cohort"] + - ") " + - cancerTypesQueryNoFPPP[i]["description"]; - currentOption.id = cancerTypesQueryNoFPPP[i]["cohort"]; - selectBox.appendChild(currentOption); - } - let cancerTypeSelectedOptions = localStorage - .getItem("cancerTypeSelectedOptions") || null - if (cancerTypeSelectedOptions) { - cancerTypeSelectedOptions = cancerTypeSelectedOptions.split(","); - $(".cancerTypeMultipleSelection").val(cancerTypeSelectedOptions); - $(".cancerTypeMultipleSelection").trigger('change'); - } - - return true; + try { + const cancerTypesQuery = await firebrowse.fetchCohorts(); + const cancerTypesQueryNoFPPP = cancerTypesQuery.reduce((acc, item) => item.cohort !== 'FPPP' ? [...acc, item] : acc, []); + cancerTypesQueryNoFPPP.sort(); + let selectBox = document.getElementById("cancerTypeMultipleSelection"); + for (let i = 0; i < cancerTypesQueryNoFPPP.length; i++) { + let currentOption = document.createElement("option"); + currentOption.value = cancerTypesQueryNoFPPP[i]["cohort"]; + currentOption.text = + "(" + + cancerTypesQueryNoFPPP[i]["cohort"] + + ") " + + cancerTypesQueryNoFPPP[i]["description"]; + currentOption.id = cancerTypesQueryNoFPPP[i]["cohort"]; + selectBox.appendChild(currentOption); + } + let cancerTypeSelectedOptions = localStorage + .getItem("cancerTypeSelectedOptions") || null + if (cancerTypeSelectedOptions) { + cancerTypeSelectedOptions = cancerTypeSelectedOptions.split(","); + $(".cancerTypeMultipleSelection").val(cancerTypeSelectedOptions); + $(".cancerTypeMultipleSelection").trigger('change'); + } + return true; + } catch (err) { + message = "Could not fetch the cancer types from Firebrowse"; + handleDataFetchError(message); + } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -67,53 +71,58 @@ let displayNumberSamples = async function () { .select2("data") .map((cohortInfo) => cohortInfo.text.match(/\(([^)]+)\)/)[1]); if (myCohort.length != 0) { - // get counts of samples for selected tumor types: - numbersOfSamples = await firebrowse.fetchCounts(myCohort); - let formatted_numbersOfSamples = numbersOfSamples.map(x => { - const container = {}; - container.cohort = x.cohort.substring(0, numbersOfSamples[0].cohort.indexOf('-')); - container.mrnaseq = x.mrnaseq; - return container; - }); - // order counts array based on order in which tumor types were selected: - function orderThings (array, order, key) { - array.sort(function (a, b) { - var A = a[key], B = b[key]; - if (order.indexOf(A) > order.indexOf(B)) { - return 1; + try { + // get counts of samples for selected tumor types: + numbersOfSamples = await firebrowse.fetchCounts(myCohort); + let formatted_numbersOfSamples = numbersOfSamples.map(x => { + const container = {}; + container.cohort = x.cohort.substring(0, numbersOfSamples[0].cohort.indexOf('-')); + container.mrnaseq = x.mrnaseq; + return container; + }); + // order counts array based on order in which tumor types were selected: + function orderThings (array, order, key) { + array.sort(function (a, b) { + var A = a[key], B = b[key]; + if (order.indexOf(A) > order.indexOf(B)) { + return 1; + } else { + return -1; + } + }); + return array; + }; + orderedCountQuery = orderThings(formatted_numbersOfSamples, myCohort, 'cohort') + // build label: + let numSamplesLabel = ""; + let para; + for (let i = 0; i < orderedCountQuery.length; i++) { + if (numSamplesLabel == "") { + numSamplesLabel += orderedCountQuery[i].cohort + ": " + orderedCountQuery[i].mrnaseq; + para = document.createElement("P"); + para.setAttribute( + "style", + 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' + ); + para.setAttribute("id", "numSamplesText"); + para.innerText = "Number of samples: " + numSamplesLabel; + cancerQuerySelectBox.appendChild(para); } else { - return -1; + document.getElementById("numSamplesText").remove(); + numSamplesLabel += ", " + orderedCountQuery[i].cohort + + ": " + orderedCountQuery[i].mrnaseq; + para.setAttribute( + "style", + 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' + ); + para.setAttribute("id", "numSamplesText"); + para.innerText = "Number of samples: " + numSamplesLabel; + cancerQuerySelectBox.appendChild(para); } - }); - return array; - }; - orderedCountQuery = orderThings(formatted_numbersOfSamples, myCohort, 'cohort') - // build label: - let numSamplesLabel = ""; - let para; - for (let i = 0; i < orderedCountQuery.length; i++) { - if (numSamplesLabel == "") { - numSamplesLabel += orderedCountQuery[i].cohort + ": " + orderedCountQuery[i].mrnaseq; - para = document.createElement("P"); - para.setAttribute( - "style", - 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' - ); - para.setAttribute("id", "numSamplesText"); - para.innerText = "Number of samples: " + numSamplesLabel; - cancerQuerySelectBox.appendChild(para); - } else { - document.getElementById("numSamplesText").remove(); - numSamplesLabel += ", " + orderedCountQuery[i].cohort + - ": " + orderedCountQuery[i].mrnaseq; - para.setAttribute( - "style", - 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' - ); - para.setAttribute("id", "numSamplesText"); - para.innerText = "Number of samples: " + numSamplesLabel; - cancerQuerySelectBox.appendChild(para); } + } catch(err) { + message = "Could not fetch the cancer type counts from Firebrowse"; + handleDataFetchError(message); } } }; diff --git a/main/js/smartCache/main.js b/main/js/smartCache/main.js index a92a6850..1650282b 100644 --- a/main/js/smartCache/main.js +++ b/main/js/smartCache/main.js @@ -381,19 +381,24 @@ function CacheInterface(nameOfDb) { //Iterate over each cohort, gene combination for (let cohort in interface) { for (let gene in interface[cohort]) { - //Fetch expression data for requested cohort, gene, and barcodes with Firebrowse fetch call - let expressionData = await firebrowse.fetchmRNASeq({cohorts: [cohort], genes: [gene]}) - for(let index = 0; index < expressionData.length; index++) { - try { - let obj = expressionData[index]; - //Call add() and saveToDB() to cache the fetched data - await cacheGE.add(obj.cohort, obj.tcga_participant_barcode, gene, obj); - await cacheGE.saveToDB(obj.cohort, obj.tcga_participant_barcode, gene, obj); - } catch(err) { - //If an error occurred, print an error message and end execution - console.error('Failed, skipping for cohort.', err); - return undefined + try { + //Fetch expression data for requested cohort, gene, and barcodes with Firebrowse fetch call + let expressionData = await firebrowse.fetchmRNASeq({cohorts: [cohort], genes: [gene]}) + for(let index = 0; index < expressionData.length; index++) { + try { + let obj = expressionData[index]; + //Call add() and saveToDB() to cache the fetched data + await cacheGE.add(obj.cohort, obj.tcga_participant_barcode, gene, obj); + await cacheGE.saveToDB(obj.cohort, obj.tcga_participant_barcode, gene, obj); + } catch(err) { + //If an error occurred, print an error message and end execution + console.error('Failed, skipping for cohort.', err); + return undefined + } } + } catch(err) { + message = `Could not fetch/store genetic data from Firebrowse for cohort ${cohort} and gene ${gene}`; + handleDataFetchError(message); } } } @@ -582,19 +587,24 @@ function CacheInterface(nameOfDb) { //Iterate over each cohort, gene combination for (let cohort in interface) { for (let gene of interface[cohort]) { - //Fetch expression data for requested cohort, gene, and barcodes with Firebrowse fetch call - let rawMutationData = await firebrowse.fetchMutationMAF({cohorts: [cohort], genes: [gene]}); - let mutationData = await formatMutationData(cohort, gene, rawMutationData); - for(let index = 0; index < mutationData.length; index++) { - try { - let obj = mutationData[index]; - await cacheMU.add(obj.cohort, obj.tcga_participant_barcode, gene, obj); // Add patient to mutation data caching interface - await cacheMU.saveToDB(obj.cohort, obj.tcga_participant_barcode, gene, obj); // Save patient to caching interface database - } catch(err) { - //If an error occurred, print an error message and end execution - console.error('Failed, skipping for cohort.', err); - return undefined + try { + //Fetch expression data for requested cohort, gene, and barcodes with Firebrowse fetch call + let rawMutationData = await firebrowse.fetchMutationMAF({cohorts: [cohort], genes: [gene]}); + let mutationData = await formatMutationData(cohort, gene, rawMutationData); + for(let index = 0; index < mutationData.length; index++) { + try { + let obj = mutationData[index]; + await cacheMU.add(obj.cohort, obj.tcga_participant_barcode, gene, obj); // Add patient to mutation data caching interface + await cacheMU.saveToDB(obj.cohort, obj.tcga_participant_barcode, gene, obj); // Save patient to caching interface database + } catch(err) { + //If an error occurred, print an error message and end execution + console.error('Failed, skipping for cohort.', err); + return undefined + } } + } catch(err) { + message = `Could not fetch/store mutation data from Firebrowse for cohort ${cohort} and gene ${gene}`; + handleDataFetchError(message) } } } @@ -679,29 +689,33 @@ function CacheInterface(nameOfDb) { async function executeQueriesBAR(interface) { //Harcode gene to query mRNASeq data for let expr = "TP53" - //For each cohort, query the mRNASeq data for that cohort - for (let cohort in interface) { - //Get mRNASeq data with hardcoded gene - /*Currently uses low-level firebrowse function to fetch mRNASeq data; - replace with appropriate gene expression caching method!*/ - let expressionData = await firebrowse.fetchmRNASeq({ - cohorts: [cohort], - genes: [expr], - }) - //Iterate over each JSON object in fetched expression data and save to cache - for(let index = 0; index < expressionData.length; index++) { - let obj = expressionData[index]; + //For each cohort, query the mRNASeq data for that cohort + for (let cohort in interface) { try { - //Call add() and saveToDB() to cache the fetched data - await cacheBAR.add(obj.cohort, obj.tcga_participant_barcode); - await cacheBAR.saveToDB(obj.cohort, obj.tcga_participant_barcode, obj); - } catch(err) { - //If an error occurred, print an error message and end execution - console.error('Failed, skipping for cohort.', err); - return undefined + //Get mRNASeq data with hardcoded gene + /*Currently uses low-level firebrowse function to fetch mRNASeq data; + replace with appropriate gene expression caching method!*/ + let expressionData = await firebrowse.fetchmRNASeq({ + cohorts: [cohort], + genes: [expr], + }) + //Iterate over each JSON object in fetched expression data and save to cache + for(let index = 0; index < expressionData.length; index++) { + let obj = expressionData[index]; + try { + //Call add() and saveToDB() to cache the fetched data + await cacheBAR.add(obj.cohort, obj.tcga_participant_barcode); + await cacheBAR.saveToDB(obj.cohort, obj.tcga_participant_barcode, obj); + } catch(err) { + //If an error occurred, print an error message and end execution + console.error('Failed, skipping for cohort.', err); + return undefined + } + }} catch(err) { + message = `Could not fetch/store barcode data from Firebrowse for cohort ${cohort}`; + handleDataFetchError(message); } } - } } //missingInterface is an array of cohorts whose barcodes have not yet been cached @@ -766,29 +780,35 @@ function CacheInterface(nameOfDb) { ) .prop('disabled', true) .trigger('change.select2'); - for (let cohort in interface) { - let getBarcodesInACohort = barcodesByCohort.filter(cohortEle => (cohortEle.cohort == cohort))[0].barcodes - let clinicalData = await firebrowse.fetchClinicalFH({ - cohorts: [cohort], - barcodes: getBarcodesInACohort, - }).then((clinicalData) => { + + for (let cohort in interface) { + try { + let getBarcodesInACohort = barcodesByCohort.filter(cohortEle => (cohortEle.cohort == cohort))[0].barcodes + let clinicalData = await firebrowse.fetchClinicalFH({ + cohorts: [cohort], + barcodes: getBarcodesInACohort, + }) // Iterate over each patient's clinical data for(let index = 0; index < clinicalData.length; index++) { + try { let obj = clinicalData[index]; - cacheCLIN.add(cohort=obj.cohort, barcode=obj); // Add clinical data to interface map by mimicking parameters for barcode caching + cacheCLIN.add(cohort = obj.cohort, barcode = obj); // Add clinical data to interface map by mimicking parameters for barcode caching cacheCLIN.saveToDB(obj.cohort, obj.tcga_participant_barcode, obj); // Save clinical data to interface - } - }).catch(error => { - console.error('Failed, skipping for cohort.', error); - return undefined - }); + } catch(err) { + console.error('Failed, skipping for cohort.', error); + return undefined + }; + }} catch(err) { + message = `Could not fetch/store clinical data from Firebrowse for cohort ${cohort}`; + handleDataFetchError(message); + } } $( '.clinicalMultipleSelection, .pathwayMultipleSelection' ) .prop('disabled', false) .trigger('change.select2'); - } + } let [missingInterface, hasInterface] = constructQueriesCLIN(listOfCohorts, this.interface)