From 1472e6c2938b1846c861d33db931bff66c7e28e9 Mon Sep 17 00:00:00 2001 From: haynescd Date: Tue, 9 May 2023 10:19:08 -0400 Subject: [PATCH 1/2] :sparkles: Add PatientIdentifiers URL Parameter --- src/pages/studyView/StudyViewPageStore.ts | 71 ++++++++++++++++++++--- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/src/pages/studyView/StudyViewPageStore.ts b/src/pages/studyView/StudyViewPageStore.ts index e008c43da76..1d189395218 100644 --- a/src/pages/studyView/StudyViewPageStore.ts +++ b/src/pages/studyView/StudyViewPageStore.ts @@ -57,6 +57,7 @@ import { PatientTreatmentRow, ResourceData, Sample, + SampleFilter, SampleIdentifier, SampleMolecularIdentifier, SampleTreatmentRow, @@ -389,6 +390,15 @@ enum CustomDataTypeEnum { NUMERICAL = 'NUMERICAL', } +interface PatientIdentifierFilter { + patientIdentifiers: PatientIdentifier[]; +} + +interface PatientIdentifier { + patientId: string; + studyId: string; +} + export class StudyViewPageStore implements IAnnotationFilterSettings, ISettingsMenuButtonVisible { private reactionDisposers: IReactionDisposer[] = []; @@ -2165,14 +2175,26 @@ export class StudyViewPageStore // We do not support studyIds in the query filters let filters: Partial = {}; if (query.filterJson) { - try { - filters = JSON.parse( - decodeURIComponent(query.filterJson) - ) as Partial; - this.updateStoreByFilters(filters); - } catch (e) { - // TODO: add some logging here? + const parsedFilterJson = this.parseRawFilterJson(query.filterJson); + if (query.filterJson.includes('patientIdentifiers')) { + const sampleListIds = studyIds.map(s => s.concat('', '_all')); + const samples = await this.fetchSamplesWithSampleListIds( + sampleListIds + ); + const { + patientIdentifiers, + } = parsedFilterJson as PatientIdentifierFilter; + const sampleIdentifiers = this.convertPatientIdentifiersToSampleIdentifiers( + patientIdentifiers, + samples + ); + if (sampleIdentifiers.length > 0) { + filters.sampleIdentifiers = sampleIdentifiers; + } + } else { + filters = parsedFilterJson as Partial; } + this.updateStoreByFilters(filters); } else if (query.filterAttributeId && query.filterValues) { const clinicalAttributes = _.uniqBy( await defaultClient.fetchClinicalAttributesUsingPOST({ @@ -2224,6 +2246,41 @@ export class StudyViewPageStore } } + parseRawFilterJson(filterJson: string): any { + let rawJson; + try { + rawJson = JSON.parse(decodeURIComponent(filterJson)); + } catch (e) { + console.error('FilterJson invalid Json: error: ', e); + } + return rawJson; + } + + fetchSamplesWithSampleListIds(sampleListIds: string[]) { + return defaultClient.fetchSamplesUsingPOST({ + sampleFilter: { + sampleListIds: sampleListIds, + } as SampleFilter, + projection: 'SUMMARY', + }); + } + + convertPatientIdentifiersToSampleIdentifiers( + patientIdentifiers: Array, + samples: Sample[] + ): SampleIdentifier[] { + return samples + .filter(s => + patientIdentifiers.some( + p => p.patientId === s.patientId && p.studyId === s.studyId + ) + ) + .map(s => ({ + sampleId: s.sampleId, + studyId: s.studyId, + })); + } + @computed get initialFilters(): StudyViewFilter { let initialFilter = {} as StudyViewFilter; From 8273f1fc47c177b9b9adb9d08ce4aafaf5d3427d Mon Sep 17 00:00:00 2001 From: haynescd Date: Thu, 11 May 2023 16:12:32 -0400 Subject: [PATCH 2/2] Update with PR comments --- src/pages/studyView/StudyViewPage.tsx | 9 ++++- src/pages/studyView/StudyViewPageStore.ts | 45 ++++++++++++--------- src/shared/model/PatientIdentifierFilter.ts | 8 ++++ 3 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 src/shared/model/PatientIdentifierFilter.ts diff --git a/src/pages/studyView/StudyViewPage.tsx b/src/pages/studyView/StudyViewPage.tsx index f485d25d8e3..3fae2de9aa1 100644 --- a/src/pages/studyView/StudyViewPage.tsx +++ b/src/pages/studyView/StudyViewPage.tsx @@ -189,13 +189,18 @@ export default class StudyViewPage extends React.Component< newStudyViewFilter.sharedCustomData = params.sharedCustomData; } } + + let updateStoreFromURLPromise = remoteData(() => Promise.resolve([])); if (!_.isEqual(newStudyViewFilter, this.store.studyViewQueryFilter)) { - this.store.updateStoreFromURL(newStudyViewFilter); this.store.studyViewQueryFilter = newStudyViewFilter; + updateStoreFromURLPromise = remoteData(async () => { + await this.store.updateStoreFromURL(newStudyViewFilter); + return []; + }); } onMobxPromise( - this.store.queriedPhysicalStudyIds, + [this.store.queriedPhysicalStudyIds, updateStoreFromURLPromise], (strArr: string[]) => { this.store.initializeReaction(); trackEvent({ diff --git a/src/pages/studyView/StudyViewPageStore.ts b/src/pages/studyView/StudyViewPageStore.ts index 1d189395218..e516edf6103 100644 --- a/src/pages/studyView/StudyViewPageStore.ts +++ b/src/pages/studyView/StudyViewPageStore.ts @@ -268,6 +268,10 @@ import { PageType } from 'shared/userSession/PageType'; import { FeatureFlagEnum } from 'shared/featureFlags'; import intersect from 'fast_array_intersect'; import { PillStore } from 'shared/components/PillTag/PillTag'; +import { + PatientIdentifier, + PatientIdentifierFilter, +} from 'shared/model/PatientIdentifierFilter'; export const STUDY_VIEW_FILTER_AUTOSUBMIT = 'study_view_filter_autosubmit'; @@ -390,15 +394,6 @@ enum CustomDataTypeEnum { NUMERICAL = 'NUMERICAL', } -interface PatientIdentifierFilter { - patientIdentifiers: PatientIdentifier[]; -} - -interface PatientIdentifier { - patientId: string; - studyId: string; -} - export class StudyViewPageStore implements IAnnotationFilterSettings, ISettingsMenuButtonVisible { private reactionDisposers: IReactionDisposer[] = []; @@ -2181,16 +2176,10 @@ export class StudyViewPageStore const samples = await this.fetchSamplesWithSampleListIds( sampleListIds ); - const { - patientIdentifiers, - } = parsedFilterJson as PatientIdentifierFilter; - const sampleIdentifiers = this.convertPatientIdentifiersToSampleIdentifiers( - patientIdentifiers, + filters = this.getStudyViewFilterFromPatientIdentifierFilter( + parsedFilterJson as PatientIdentifierFilter, samples ); - if (sampleIdentifiers.length > 0) { - filters.sampleIdentifiers = sampleIdentifiers; - } } else { filters = parsedFilterJson as Partial; } @@ -2265,15 +2254,31 @@ export class StudyViewPageStore }); } + getStudyViewFilterFromPatientIdentifierFilter( + patientIdentifierFilter: PatientIdentifierFilter, + samples: Sample[] + ): Partial { + const filters: Partial = {}; + const sampleIdentifiers = this.convertPatientIdentifiersToSampleIdentifiers( + patientIdentifierFilter.patientIdentifiers, + samples + ); + if (sampleIdentifiers.length > 0) { + filters.sampleIdentifiers = sampleIdentifiers; + } + return filters; + } + convertPatientIdentifiersToSampleIdentifiers( patientIdentifiers: Array, samples: Sample[] ): SampleIdentifier[] { + const patientIdentifiersMap = new Map( + patientIdentifiers.map(p => [p.studyId.concat('_', p.patientId), p]) + ); return samples .filter(s => - patientIdentifiers.some( - p => p.patientId === s.patientId && p.studyId === s.studyId - ) + patientIdentifiersMap.has(s.studyId.concat('_', s.patientId)) ) .map(s => ({ sampleId: s.sampleId, diff --git a/src/shared/model/PatientIdentifierFilter.ts b/src/shared/model/PatientIdentifierFilter.ts new file mode 100644 index 00000000000..5758f3ddb0c --- /dev/null +++ b/src/shared/model/PatientIdentifierFilter.ts @@ -0,0 +1,8 @@ +export interface PatientIdentifierFilter { + patientIdentifiers: PatientIdentifier[]; +} + +export interface PatientIdentifier { + patientId: string; + studyId: string; +}