diff --git a/src/pages/patientView/PatientViewPage.tsx b/src/pages/patientView/PatientViewPage.tsx index fa027c4a2e3..50ad8951fd6 100644 --- a/src/pages/patientView/PatientViewPage.tsx +++ b/src/pages/patientView/PatientViewPage.tsx @@ -317,12 +317,37 @@ export class PatientViewPageInner extends React.Component< @computed public get shouldShowMtbTab(): boolean { - return true; + return ( + this.pageStore.mutationData.isComplete && + this.pageStore.discreteCNAData.isComplete && + (this.pageStore.oncoKbData.isComplete || + this.pageStore.oncoKbData.isError) && + (this.pageStore.mtbs.isComplete || this.pageStore.mtbs.isError) && + (this.pageStore.cnaOncoKbData.isComplete || + this.pageStore.cnaOncoKbData.isError) + ); } @computed public get shouldShowFollowUpTab(): boolean { - return true; + return ( + this.pageStore.mutationData.isComplete && + this.pageStore.followUps.isComplete && + this.pageStore.discreteCNAData.isComplete && + (this.pageStore.oncoKbData.isComplete || + this.pageStore.oncoKbData.isError) && + (this.pageStore.mtbs.isComplete || this.pageStore.mtbs.isError) && + (this.pageStore.cnaOncoKbData.isComplete || + this.pageStore.cnaOncoKbData.isError) + ); + } + + @computed + public get shouldShowPatientSimilarity(): boolean { + return ( + //this.pageStore.similarPatientsPage.isComplete + true + ); } @autobind diff --git a/src/pages/patientView/PatientViewPageTabs.tsx b/src/pages/patientView/PatientViewPageTabs.tsx index 86c84489da1..6e4de9d5062 100644 --- a/src/pages/patientView/PatientViewPageTabs.tsx +++ b/src/pages/patientView/PatientViewPageTabs.tsx @@ -27,6 +27,8 @@ import SampleManager from 'pages/patientView/SampleManager'; import PatientViewPage from 'pages/patientView/PatientViewPage'; import PatientViewUrlWrapper from 'pages/patientView/PatientViewUrlWrapper'; import { ClinicalTrialMatchTable } from './clinicalTrialMatch/ClinicalTrialMatchTable'; +//import { PatientSimilarityTable } from './patientSimilarity/patientSimilarityTable' +import PatientSimilarityTable from './patientSimilarity/patientSimilarityTable'; import MtbTable from './therapyRecommendation/MtbTable'; import { CompactVAFPlot } from 'pages/patientView/genomicOverview/CompactVAFPlot'; import { @@ -55,6 +57,7 @@ export enum PatientViewPageTabs { Mtb = 'mtb', FollowUp = 'followUp', ClinicalTrialsGov = 'clinicaltrialsGov', + PatientSimilarity = 'patientSimilarity', } export const PatientViewResourceTabPrefix = 'openResource_'; @@ -651,14 +654,6 @@ export function tabs( ); pageComponent.shouldShowMtbTab && - pageComponent.patientViewPageStore.mutationData.isComplete && - pageComponent.patientViewPageStore.discreteCNAData.isComplete && - (pageComponent.patientViewPageStore.oncoKbData.isComplete || - pageComponent.patientViewPageStore.oncoKbData.isError) && - (pageComponent.patientViewPageStore.mtbs.isComplete || - pageComponent.patientViewPageStore.mtbs.isError) && - (pageComponent.patientViewPageStore.cnaOncoKbData.isComplete || - pageComponent.patientViewPageStore.cnaOncoKbData.isError) && tabs.push( ); + pageComponent.shouldShowPatientSimilarity && + tabs.push( + +

HELLO WORLD

+ +
+ ); + pageComponent.resourceTabs.component && /* @ts-ignore */ tabs.push(...pageComponent.resourceTabs.component); diff --git a/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts b/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts index a0bf3fb49f6..a0a7cd29b10 100644 --- a/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts +++ b/src/pages/patientView/clinicalInformation/PatientViewPageStore.ts @@ -250,6 +250,7 @@ import { import { RecruitingStatus } from 'shared/enums/ClinicalTrialsGovRecruitingStatus'; import { ageAsNumber } from '../clinicalTrialMatch/utils/AgeSexConverter'; import { City } from '../clinicalTrialMatch/ClinicalTrialMatchSelectUtil'; +import { SimilarPatient } from 'shared/api/SimilarPatientsAPI'; type PageMode = 'patient' | 'sample'; type ResourceId = string; @@ -3498,4 +3499,39 @@ export class PatientViewPageStore { default: {}, onError: () => {}, }); + + ////fetchTest() + //readonly getPatients = remoteData({ + // invoke: async () => { + // var result: Patient[] = [] + // (await fetchPatients()).map((x) => { + // result.push(x.uniquePatientKey) + // }); + // return result + // }, + // default: [] + //}); + // + // + //readonly similarPatientsPage = remoteData({ + // await: () => [this.getPatientIds], + // invoke: async () => { + // var result: SimilarPatient[] = []; + // + // + // console.group('### TEST ###'); + // console.log(this.getPatientIds.result) + // console.groupEnd(); + // + // for (const patientId of this.getPatientIds.result) { + // //fetchClinicalDataForPatient + // result.push({ + // id: patientId + // }) + // } + // + // + // return result + // } + //}) } diff --git a/src/pages/patientView/patientSimilarity/patientSimilarityTable.tsx b/src/pages/patientView/patientSimilarity/patientSimilarityTable.tsx new file mode 100644 index 00000000000..7457628da96 --- /dev/null +++ b/src/pages/patientView/patientSimilarity/patientSimilarityTable.tsx @@ -0,0 +1,213 @@ +import * as React from 'react'; +import { observable } from 'mobx'; +import { PatientViewPageStore } from '../clinicalInformation/PatientViewPageStore'; +import { observer } from 'mobx-react'; +import { Link } from 'react-router-dom'; +import { Collapse } from 'react-bootstrap'; +import LazyMobXTable from '../../../shared/components/lazyMobXTable/LazyMobXTable'; +import LoadingIndicator from 'shared/components/loadingIndicator/LoadingIndicator'; +import { DefaultTooltip } from 'cbioportal-frontend-commons'; +import { Button } from 'react-bootstrap'; +import { IClinicalTrial } from 'cbioportal-utils'; +import { + SimilarPatient, + fetchPatientsPage, +} from 'shared/api/SimilarPatientsAPI'; +import { getServerConfig } from 'config/config'; + +import { remoteData } from 'cbioportal-frontend-commons'; +import { sleep } from 'shared/lib/TimeUtils'; + +enum ColumnKey { + //patient_id: string; + //study_id: string; + //age: number; + //gender: string; + //name: string; + //cancertype: string; + STUDY = 'study', + NAME = 'name', + AGE = 'age', + GENDER = 'gender', + CANCERTYPE = 'cancertype', +} + +interface PatientSimilarityProps { + store: PatientViewPageStore; +} + +class SimilarPatientEntry { + private id; + constructor(id: string) { + this.id = id; + } + get_id(): string { + return this.id; + } +} + +class SimilarPatientTableComponent extends LazyMobXTable {} + +//@observer +//export class PatientSimilarityTable extends React.Component< +// PatientSimilarityProps, +// {} +//> { +// private readonly ENTRIES_PER_PAGE = 10; +// private _columns = [ +// { +// name: ColumnKey.PATIENTID, +// render: (patient: SimilarPatient) => ( +//
+// {patient.id} +//
+// ), +// width: 250, +// resizable: true, +// }, +// ]; +// +// @observable +// patients: SimilarPatientEntry[] = []; +// +// constructor(props: PatientSimilarityProps) { +// super(props); +// this.state = { +// +// }; +// } +// +// private current_page = 0; +// +// render() { +// return ( +//
+//
+// +//
+//
+// +//
+//
+// ); +// } +//} + +const PatientSimilarityTable = (props: PatientSimilarityProps) => { + const [currentPage, setCurrentPage] = React.useState(0); + const [perPage, setPerPage] = React.useState(1); + const [totalPages, setTotalPages] = React.useState(2); + const [patients, setPatients] = React.useState([]); + + const _columns = [ + { + name: ColumnKey.STUDY, + render: (patient: SimilarPatient) =>
{patient.study_id}
, + width: 250, + resizable: true, + }, + { + name: ColumnKey.NAME, + render: (patient: SimilarPatient) =>
{patient.name}
, + width: 250, + resizable: true, + }, + { + name: ColumnKey.AGE, + render: (patient: SimilarPatient) =>
{patient.age}
, + width: 250, + resizable: true, + }, + { + name: ColumnKey.GENDER, + render: (patient: SimilarPatient) =>
{patient.gender}
, + width: 250, + resizable: true, + }, + { + name: ColumnKey.CANCERTYPE, + render: (patient: SimilarPatient) => ( +
{patient.cancertype}
+ ), + width: 250, + resizable: true, + }, + ]; + + React.useEffect(() => { + function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + // fetch data + (async function() { + try { + const { patients, totalPages } = await fetchPatientsPage( + currentPage, + perPage + ); + await sleep(50); // sometimes the re-render would not be triggered when fetch was too fast... any ideas why this happens? + + //console.group('### TEST RELOAD PATIENTS ###'); + //console.log(patients) + //console.groupEnd(); + + setPatients(patients); + setTotalPages(totalPages); + } catch (error) { + console.error('Error fetching data:', error); + } + })(); + }, [currentPage, perPage]); + + const handlePrevPage = () => { + if (currentPage > 0) { + setCurrentPage(currentPage - 1); + } + }; + + const handleNextPage = () => { + if (currentPage < totalPages) { + setCurrentPage(currentPage + 1); + } + }; + + return ( +
+ {/* Display table */} +
{currentPage}
+
{JSON.stringify(patients)}
+ + + {/* Pagination controls */} + + +
+ ); +}; + +export default PatientSimilarityTable; diff --git a/src/shared/api/SimilarPatientsAPI.ts b/src/shared/api/SimilarPatientsAPI.ts new file mode 100644 index 00000000000..e37ae107e16 --- /dev/null +++ b/src/shared/api/SimilarPatientsAPI.ts @@ -0,0 +1,203 @@ +import { + IMtb, + IDeletions, + ITherapyRecommendation, + IFollowUp, +} from 'cbioportal-utils'; +import * as request from 'superagent'; +import { fetchTrialMatchesUsingPOST } from './MatchMinerAPI'; +import client from './cbioportalClientInstance'; +import defaultClient from './cbioportalClientInstance'; +import { + ClinicalData, + CBioPortalAPI, + MutationFilter, + Mutation, +} from 'cbioportal-ts-api-client'; +import { + concatMutationData, + existsSomeMutationWithAscnPropertyInCollection, + fetchClinicalData, + fetchClinicalDataForPatient, + fetchCnaOncoKbData, + fetchCnaOncoKbDataForOncoprint, + fetchCopyNumberData, + fetchCopyNumberSegments, + fetchCosmicData, + fetchDiscreteCNAData, + fetchGisticData, + fetchMutationData, + fetchMutSigData, + fetchOncoKbCancerGenes, + fetchOncoKbData, + fetchOncoKbDataForOncoprint, + fetchOncoKbInfo, + fetchReferenceGenomeGenes, + fetchSamplesForPatient, + fetchStudiesForSamplesWithoutCancerTypeClinicalData, + fetchVariantAnnotationsIndexedByGenomicLocation, + filterAndAnnotateMolecularData, + filterAndAnnotateMutations, + findDiscreteMolecularProfile, + findMolecularProfileIdDiscrete, + findMrnaRankMolecularProfileId, + findMutationMolecularProfile, + findSamplesWithoutCancerTypeClinicalData, + findUncalledMutationMolecularProfileId, + generateUniqueSampleKeyToTumorTypeMap, + getGenomeNexusUrl, + getOtherBiomarkersQueryId, + getSampleClinicalDataMapByKeywords, + getSampleClinicalDataMapByThreshold, + getSampleTumorTypeMap, + groupBySampleId, + makeGetOncoKbCnaAnnotationForOncoprint, + makeGetOncoKbMutationAnnotationForOncoprint, + makeIsHotspotForOncoprint, + makeStudyToCancerTypeMap, + mapSampleIdToClinicalData, + mergeDiscreteCNAData, + mergeMutations, + mergeMutationsIncludingUncalled, + ONCOKB_DEFAULT, + generateStructuralVariantId, + fetchStructuralVariantOncoKbData, + parseOtherBiomarkerQueryId, + tumorTypeResolver, + evaluatePutativeDriverInfoWithHotspots, + evaluatePutativeDriverInfo, +} from 'shared/lib/StoreUtils'; + +export interface SimilarPatient { + patient_id: string; + study_id: string; + age: number; + gender: string; + name: string; + cancertype: string; +} + +export async function fetchPatientsPage( + page: number = 0, + pageSize: number = 10, + client: CBioPortalAPI = defaultClient +) { + //'keyword'?: string; + //'projection'?: "ID" | "SUMMARY" | "DETAILED" | "META"; + //'pageSize'?: number; + //'pageNumber'?: number; + //'direction'?: "ASC" | "DESC"; + //$queryParameters?: any; + var patients: SimilarPatient[] = []; + + const rawPatients = await client.getAllPatientsUsingGET({ + projection: 'SUMMARY', + pageSize: pageSize, + pageNumber: page, + }); + + const totalPages: number = 2; + + //console.log(rawPatients) + + rawPatients.forEach(patient => { + (async function(patient) { + // GET CLINICAL DATA + const currentClinicalData = await client.getAllClinicalDataOfPatientInStudyUsingGET( + { + studyId: patient.studyId, + patientId: patient.patientId, + } + ); + var clinicalDataDict = clinicalData2Dict(currentClinicalData); + + //console.group('### TEST ###'); + //console.log(clinicalDataDict) + //console.groupEnd(); + + // GET SAMPLES / molecular profile ids + + // GET MUTATIONS + const mutationFilter = { + sampleIds: this.sampleIds, + } as MutationFilter; + + //const mutationData = await client.fetchMutationsInMolecularProfileUsingPOST({ + // molecularProfileId, + // mutationFilter, + // projection: 'DETAILED', + //}) + + const mutationData = await fetchMutationData( + mutationFilter, + molecularProfileId + ); + + // COLLECT DATA + patients.push({ + patient_id: patient.patientId, + study_id: patient.studyId, + age: getOrDefault(clinicalDataDict, 'AGE', undefined, Number), + gender: getOrDefault(clinicalDataDict, 'GENDER'), + name: getOrDefault(clinicalDataDict, 'PATIENT_DISPLAY_NAME'), + cancertype: getOrDefault(clinicalDataDict, 'TEST'), + }); + })(patient); + + console.group('### TEST INITIAL PATIENTS ###'); + console.log(patients); + console.groupEnd(); + }); + + return { patients: patients, totalPages: totalPages }; +} + +function getOrDefault( + dict: { [id: string]: any }, + key: string, + dflt: any = undefined, + conversion = (x: any) => { + return x; + } +) { + return key in dict ? conversion(dict[key]) : dflt; +} + +function clinicalData2Dict(clinicalData: ClinicalData[]) { + var result: { [id: string]: string } = {}; + clinicalData.forEach(currentData => { + result[currentData.clinicalAttributeId] = currentData.value; + }); + return result; +} + +//export async function fetchSimilarPatientsPage(url: string) { +// console.log('### similar patient ### Calling GET: ' + url); +// return request +// .get(url) +// .timeout(120000) +// .then(res => { +// if (res.ok) { +// console.group('### similar patient ### Success GETting ' + url); +// console.log(JSON.parse(res.text)); +// console.groupEnd(); +// const response = JSON.parse(res.text); +// return [] as SimilarPatient[]; +// } else { +// console.group( +// '### similar patient ### ERROR res not ok GETting ' + url +// ); +// console.log(JSON.parse(res.text)); +// console.groupEnd(); +// +// return [] as SimilarPatient[]; +// } +// }) +// .catch(err => { +// console.group('### similar patient ### ERROR catched GETting ' + url); +// console.log(err); +// console.groupEnd(); +// +// return [] as SimilarPatient[]; +// }); +//} diff --git a/src/shared/components/simpleTable/SimpleTable.tsx b/src/shared/components/simpleTable/SimpleTable.tsx index dc244f27605..d44850b1897 100644 --- a/src/shared/components/simpleTable/SimpleTable.tsx +++ b/src/shared/components/simpleTable/SimpleTable.tsx @@ -25,7 +25,7 @@ export default class SimpleTable extends React.Component< {noRowsText || 'There are no results.'}