diff --git a/packages/core/src/features/facets/data/facet_additional_range_data.json b/packages/core/src/features/facets/data/facet_additional_range_data.json deleted file mode 100644 index 65e82d382a..0000000000 --- a/packages/core/src/features/facets/data/facet_additional_range_data.json +++ /dev/null @@ -1,434 +0,0 @@ -{ - "demographic.age_at_index": { - "minimum": 0, - "maximum": 89 - }, - "demographic.days_to_birth": { - "minimum": -32872, - "maximum": 0 - }, - "demographic.days_to_death": { - "minimum": -32872, - "maximum": 32872 - }, - "demographic.occupation_duration_years": { - "minimum": 0, - "maximum": 89 - }, - "demographic.weeks_gestation_at_birth": { - "minimum": 0 - }, - "demographic.year_of_birth": { - "minimum": 1900 - }, - "demographic.year_of_death": { - "minimum": 1900 - }, - "portions.creation_datetime": { - "minimum": 0 - }, - "portions.weight": { - "minimum": 0 - }, - "samples.portions.analytes.aliquots.aliquot_quantity": { - "minimum": 0 - }, - "samples.portions.analytes.aliquots.aliquot_volume": { - "minimum": 0 - }, - "samples.portions.analytes.aliquots.amount": { - "minimum": 0 - }, - "samples.portions.analytes.aliquots.concentration": { - "minimum": 0 - }, - "family_histories.relatives_with_cancer_history_count": { - "minimum": 0 - }, - "samples.portions.slides.number_proliferating_cells": { - "minimum": 0 - }, - "samples.portions.slides.percent_follicular_component": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_rhabdoid_features": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_sarcomatoid_features": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_tumor_cells": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_tumor_nuclei": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_normal_cells": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_necrosis": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_stromal_cells": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_inflam_infiltration": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_lymphocyte_infiltration": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_monocyte_infiltration": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_granulocyte_infiltration": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_neutrophil_infiltration": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.percent_eosinophil_infiltration": { - "minimum": 0, - "maximum": 100 - }, - "samples.portions.slides.prostatic_chips_positive_count": { - "minimum": 0 - }, - "samples.portions.slides.prostatic_chips_total_count": { - "minimum": 0 - }, - "samples.portions.slides.prostatic_involvement_percent": { - "minimum": 0, - "maximum": 100 - }, - "follow_ups.body_surface_area": { - "minimum": 0 - }, - "follow_ups.bmi": { - "minimum": 0 - }, - "follow_ups.cd4_count": { - "minimum": 0 - }, - "follow_ups.days_to_adverse_event": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.days_to_comorbidity": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.days_to_follow_up": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.days_to_imaging": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.days_to_progression": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.days_to_progression_free": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.days_to_recurrence": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.dlco_ref_predictive_percent": { - "minimum": 0, - "maximum": 100 - }, - "follow_ups.fev1_ref_post_bronch_percent": { - "minimum": 0, - "maximum": 100 - }, - "follow_ups.fev1_ref_pre_bronch_percent": { - "minimum": 0, - "maximum": 100 - }, - "follow_ups.fev1_fvc_pre_bronch_percent": { - "minimum": 0, - "maximum": 100 - }, - "follow_ups.fev1_fvc_post_bronch_percent": { - "minimum": 0, - "maximum": 100 - }, - "follow_ups.height": { - "minimum": 0 - }, - "follow_ups.hiv_viral_load": { - "minimum": 0 - }, - "follow_ups.nadir_cd4_count": { - "minimum": 0 - }, - "follow_ups.pancreatitis_onset_year": { - "minimum": 1900 - }, - "follow_ups.recist_targeted_regions_number": { - "minimum": 0 - }, - "follow_ups.recist_targeted_regions_sum": { - "minimum": 0 - }, - "follow_ups.undescended_testis_corrected_age": { - "minimum": 0 - }, - "follow_ups.weight": { - "minimum": 0 - }, - "samples.current_weight": { - "minimum": 0 - }, - "samples.days_to_collection": { - "minimum": -32872, - "maximum": 32872 - }, - "samples.days_to_sample_procurement": { - "minimum": -32872, - "maximum": 32872 - }, - "samples.growth_rate": { - "minimum": 0 - }, - "samples.initial_weight": { - "minimum": 0 - }, - "samples.intermediate_dimension": { - "minimum": 0 - }, - "samples.longest_dimension": { - "minimum": 0 - }, - "samples.passage_count": { - "minimum": 0 - }, - "samples.sample_ordinal": { - "minimum": 1 - }, - "samples.shortest_dimension": { - "minimum": 0 - }, - "samples.time_between_clamping_and_freezing": { - "minimum": 0 - }, - "samples.time_between_excision_and_freezing": { - "minimum": 0 - }, - "samples.portions.analytes.a260_a280_ratio": { - "minimum": 0 - }, - "samples.portions.analytes.amount": { - "minimum": 0 - }, - "samples.portions.analytes.analyte_quantity": { - "minimum": 0 - }, - "samples.portions.analytes.analyte_volume": { - "minimum": 0 - }, - "samples.portions.analytes.concentration": { - "minimum": 0 - }, - "samples.portions.analytes.ribosomal_rna_28s_16s_ratio": { - "minimum": 0 - }, - "samples.portions.analytes.rna_integrity_number": { - "maximum": 10 - }, - "exposures.age_at_onset": { - "minimum": 0, - "maximum": 89 - }, - "exposures.alcohol_days_per_week": { - "minimum": 0, - "maximum": 7 - }, - "exposures.alcohol_drinks_per_day": { - "minimum": 0 - }, - "exposures.cigarettes_per_day": { - "minimum": 0 - }, - "exposures.exposure_duration_years": { - "minimum": 0, - "maximum": 89 - }, - "exposures.occupation_duration_years": { - "minimum": 0, - "maximum": 89 - }, - "exposures.pack_years_smoked": { - "minimum": 0 - }, - "exposures.tobacco_smoking_onset_year": { - "minimum": 1900, - "maximum": 2050 - }, - "exposures.tobacco_smoking_quit_year": { - "minimum": 1900, - "maximum": 2050 - }, - "exposures.years_smoked": { - "minimum": 0, - "maximum": 89 - }, - "diagnoses.pathology_details.breslow_thickness": { - "minimum": 0 - }, - "diagnoses.pathology_details.circumferential_resection_margin": { - "minimum": 0 - }, - "diagnoses.pathology_details.greatest_tumor_dimension": { - "minimum": 0 - }, - "diagnoses.pathology_details.gross_tumor_weight": { - "minimum": 0 - }, - "diagnoses.pathology_details.lymph_nodes_positive": { - "minimum": 0 - }, - "diagnoses.pathology_details.lymph_nodes_tested": { - "minimum": 0 - }, - "diagnoses.pathology_details.necrosis_percent": { - "minimum": 0, - "maximum": 100 - }, - "diagnoses.pathology_details.number_proliferating_cells": { - "minimum": 0 - }, - "diagnoses.pathology_details.percent_tumor_invasion": { - "minimum": 0, - "maximum": 100 - }, - "diagnoses.pathology_details.peripancreatic_lymph_nodes_tested": { - "minimum": 0 - }, - "diagnoses.pathology_details.prostatic_chips_positive_count": { - "minimum": 0 - }, - "diagnoses.pathology_details.prostatic_chips_total_count": { - "minimum": 0 - }, - "diagnoses.pathology_details.prostatic_involvement_percent": { - "minimum": 0, - "maximum": 100 - }, - "diagnoses.pathology_details.rhabdoid_percent": { - "minimum": 0, - "maximum": 100 - }, - "diagnoses.pathology_details.sarcomatoid_percent": { - "minimum": 0, - "maximum": 100 - }, - "diagnoses.pathology_details.size_extraocular_nodule": { - "minimum": 0 - }, - "diagnoses.pathology_details.tumor_largest_dimension_diameter": { - "minimum": 0 - }, - "diagnoses.pathology_details.tumor_thickness": { - "minimum": 0 - }, - "diagnoses.age_at_diagnosis": { - "minimum": 0, - "maximum": 32872 - }, - "diagnoses.days_to_best_overall_response": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.days_to_diagnosis": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.days_to_last_follow_up": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.days_to_last_known_disease_status": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.days_to_recurrence": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.gleason_patterns_percent": { - "minimum": 0, - "maximum": 100 - }, - "diagnoses.margin_distance": { - "minimum": 0 - }, - "diagnoses.mitotic_count": { - "minimum": 0 - }, - "diagnoses.year_of_diagnosis": { - "minimum": 1900, - "maximum": 2050 - }, - "diagnoses.treatments.days_to_treatment_end": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.treatments.days_to_treatment_start": { - "minimum": -32872, - "maximum": 32872 - }, - "diagnoses.treatments.number_of_cycles": { - "minimum": 0 - }, - "diagnoses.treatments.treatment_dose": { - "minimum": 0 - }, - "days_to_lost_to_followup": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.molecular_tests.biospecimen_volume": { - "minimum": 0 - }, - "follow_ups.molecular_tests.cell_count": { - "minimum": 0 - }, - "follow_ups.molecular_tests.copy_number": { - "minimum": 0 - }, - "follow_ups.molecular_tests.days_to_test": { - "minimum": -32872, - "maximum": 32872 - }, - "follow_ups.molecular_tests.loci_abnormal_count": { - "minimum": 0 - }, - "follow_ups.molecular_tests.loci_count": { - "minimum": 0 - }, - "follow_ups.molecular_tests.mitotic_count": { - "minimum": 0 - }, - "follow_ups.molecular_tests.mitotic_total_area": { - "minimum": 0 - } -} diff --git a/packages/core/src/features/facets/facetDictionaryApi.ts b/packages/core/src/features/facets/facetDictionaryApi.ts index fa44519392..823e9d5089 100644 --- a/packages/core/src/features/facets/facetDictionaryApi.ts +++ b/packages/core/src/features/facets/facetDictionaryApi.ts @@ -1,5 +1,4 @@ -import { FacetDefinition, FacetTypes } from "./types"; -import SupplementalFacetDefinitions from "./data/facet_additional_range_data.json"; +import { FacetDefinition, FacetDefinitionResponse, FacetTypes } from "./types"; import { some, includes } from "lodash"; export const classifyFacetDatatype = (f: FacetDefinition): FacetTypes => { @@ -55,31 +54,31 @@ export const classifyFacetDatatype = (f: FacetDefinition): FacetTypes => { return "enum"; }; -interface IStringIndex { - [key: string]: any; -} - -const getRangeData = (f: FacetDefinition) => { - if (f.field in SupplementalFacetDefinitions) { - return { - minimum: (SupplementalFacetDefinitions as IStringIndex)[f.field].minimum, - maximum: (SupplementalFacetDefinitions as IStringIndex)[f.field].maximum, - }; - } else { - return undefined; - } -}; - export const processDictionaryEntries = ( - entries: Record, + entries: Record, ): Record => { return Object.keys(entries).reduce( (dict: Record, key: string) => { - dict[key] = { - ...entries[key], + const entry = { ...entries[key] }; + const constraints = entry?.constraints; + if (constraints) { + delete entry["constraints"]; + } + + let processedFacet: FacetDefinition = { + ...entry, facet_type: classifyFacetDatatype(entries[key]), - range: getRangeData(entries[key]), }; + + if (constraints?.maximum !== undefined) { + processedFacet = { ...processedFacet, maximum: constraints.maximum }; + } + + if (constraints?.minimum !== undefined) { + processedFacet = { ...processedFacet, minimum: constraints.minimum }; + } + + dict[key] = processedFacet; return dict; }, {} as Record, diff --git a/packages/core/src/features/facets/facetDictionaryApi.unit.test.ts b/packages/core/src/features/facets/facetDictionaryApi.unit.test.ts index 6ffaeb2936..c41eba9799 100644 --- a/packages/core/src/features/facets/facetDictionaryApi.unit.test.ts +++ b/packages/core/src/features/facets/facetDictionaryApi.unit.test.ts @@ -2,8 +2,7 @@ import { processDictionaryEntries, classifyFacetDatatype, } from "./facetDictionaryApi"; - -import { FacetDefinition } from "./types"; +import { FacetDefinitionResponse } from "./types"; const TestFacetDictionary = { "cases.case_id": { @@ -27,6 +26,10 @@ const TestFacetDictionary = { field: "demographic.age_at_index", full: "cases.demographic.age_at_index", type: "long", + constraints: { + minimum: 0, + maximum: 89, + }, }, "cases.demographic.age_is_obfuscated": { description: @@ -80,10 +83,8 @@ describe("test facet dictionary api functions", () => { full: "cases.demographic.age_at_index", type: "long", facet_type: "age_in_years", - range: { - minimum: 0, - maximum: 89, - }, + minimum: 0, + maximum: 89, }, "cases.demographic.age_is_obfuscated": { description: @@ -114,7 +115,7 @@ describe("test facet dictionary api functions", () => { }; const results = processDictionaryEntries( - TestFacetDictionary as Record, + TestFacetDictionary as Record, ); expect(results).toEqual(expected); }); diff --git a/packages/core/src/features/facets/facetDictionarySlice.ts b/packages/core/src/features/facets/facetDictionarySlice.ts index c9d4ea0241..6a571bd30e 100644 --- a/packages/core/src/features/facets/facetDictionarySlice.ts +++ b/packages/core/src/features/facets/facetDictionarySlice.ts @@ -4,7 +4,7 @@ import { createSlice, } from "@reduxjs/toolkit"; import { GraphQLFetchError } from "../gdcapi/gdcgraphql"; -import { FacetDefinition } from "./types"; +import { FacetDefinition, FacetDefinitionResponse } from "./types"; import { CoreDispatch, CoreState } from "src/store"; import { CoreDataSelector, @@ -30,11 +30,11 @@ const buildGraphMappingFetchError = async ( }; export const fetchFacetDictionary = createAsyncThunk< - Record, + Record, void, { dispatch: CoreDispatch; state: CoreState } >("facet/fetchFacetDictionary", async () => { - const res = await fetch(`${GDC_APP_API_AUTH}/gql/_mapping`, { + const res = await fetch(`${GDC_APP_API_AUTH}/cases/_mapping`, { headers: { Accept: "application/json", "Content-Type": "application/json", @@ -42,7 +42,23 @@ export const fetchFacetDictionary = createAsyncThunk< method: "GET", }); - if (res.ok) return res.json(); + const fileRes = await fetch(`${GDC_APP_API_AUTH}/files/_mapping`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + method: "GET", + }); + + if (res.ok && fileRes.ok) { + const caseData = await res.json(); + const fileData = await fileRes.json(); + + return { + ...caseData["_mapping"], + ...fileData["_mapping"], + }; + } throw await buildGraphMappingFetchError(res); }); diff --git a/packages/core/src/features/facets/types.ts b/packages/core/src/features/facets/types.ts index df75922657..2e1c7a428d 100644 --- a/packages/core/src/features/facets/types.ts +++ b/packages/core/src/features/facets/types.ts @@ -22,9 +22,16 @@ export type GQLDocType = | "annotations"; export type GQLIndexType = "explore" | "repository"; -export interface AllowableRange { - readonly minimum: number; - readonly maximum: number; +export interface FacetDefinitionResponse { + readonly description: string; //description from _mapping + readonly field: string; // name of field minus "case", "file" + readonly full: string; // full name of filter (e.g. prepended with case.) + readonly type: string; // type from mapping + readonly doc_type: GQLDocType; + readonly constraints?: { + readonly minimum?: number; + readonly maximum?: number; + }; } export interface FacetDefinition { @@ -34,7 +41,8 @@ export interface FacetDefinition { readonly type: string; // type from mapping readonly doc_type: GQLDocType; readonly facet_type?: string; // classified type based on type + name: e.g. age, year, enumeration, etc - readonly range?: AllowableRange; // range of value types + readonly minimum?: number; + readonly maximum?: number; readonly hasData?: boolean; readonly title?: string; } diff --git a/packages/portal-components/src/facets/CreateFacetCard.tsx b/packages/portal-components/src/facets/CreateFacetCard.tsx index d27f60da7c..fb438eba46 100644 --- a/packages/portal-components/src/facets/CreateFacetCard.tsx +++ b/packages/portal-components/src/facets/CreateFacetCard.tsx @@ -167,8 +167,8 @@ const createFacetCards = ({ } description={facet.description} rangeDatatype={facet.facet_type} - minimum={facet?.range?.minimum} - maximum={facet?.range?.maximum} + minimum={facet?.minimum} + maximum={facet?.maximum} hideIfEmpty={hideIfEmpty} hooks={{ ...(hooks as RangeFacetHooks), diff --git a/packages/portal-components/src/facets/types.ts b/packages/portal-components/src/facets/types.ts index 1aaa66d87c..5d4be8012d 100644 --- a/packages/portal-components/src/facets/types.ts +++ b/packages/portal-components/src/facets/types.ts @@ -237,11 +237,6 @@ export interface RangeBucketElement { readonly value?: number; // count of items in range } -export interface AllowableRange { - readonly minimum: number; - readonly maximum: number; -} - export interface CohortBuilderCategoryConfig { readonly label: string; readonly facets: ReadonlyArray; @@ -267,9 +262,13 @@ export type FacetCardDefinition = { */ readonly facet_type?: string; /** - * specific field for numeric range facets, specifies the min/max bounds of the values + * specifies the min bounds of the values + */ + readonly minimum?: number; + /** + * specifies the max bounds of the values */ - readonly range?: AllowableRange; + readonly maximum?: number; /** * human readable name, if not supplied will use facetNameFormatter */