Skip to content

Commit 9274874

Browse files
committed
Gene-specific chart: added test cases, minor fixes
pretty
1 parent 1d12552 commit 9274874

File tree

3 files changed

+140
-105
lines changed

3 files changed

+140
-105
lines changed

src/pages/studyView/StudyViewPageStore.ts

Lines changed: 54 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ import {
187187
updateCustomIntervalFilter,
188188
invokeGenomicDataCount,
189189
invokeGenomicDataCountIncludeSampleid,
190+
groupSamplesByMutationStatus,
190191
invokeMutationDataCount,
191192
invokeNamespaceDataCount,
192193
invokeGenericAssayDataCount,
@@ -1643,115 +1644,69 @@ export class StudyViewPageStore
16431644
): Promise<string> {
16441645
statusCallback(LoadingPhase.DOWNLOADING_GROUPS);
16451646

1646-
// will come back to this
1647-
const promises: any = [this.selectedSamples];
1648-
16491647
return new Promise<string>(resolve => {
1650-
onMobxPromise<any>(
1651-
promises,
1652-
async (
1653-
selectedSamples: Sample[] // do we need to await this because the backend produces the sample set
1654-
) => {
1655-
const chartInfo = this._geneSpecificChartMap.get(
1656-
chartMeta.uniqueKey
1657-
);
1658-
// TODO: Replace `any` with GenomicDataCountItem[] when the type in the frontend has been updated
1659-
let data: any[] = [];
1660-
if (!chartInfo || !this.hasFilteredSamples) {
1661-
return;
1662-
}
1663-
1664-
data = await invokeGenomicDataCountIncludeSampleid(
1665-
chartInfo,
1666-
this.filters,
1667-
true
1668-
);
1669-
1670-
const SKIP_VALUES = new Set(['NOT_PROFILED']);
1671-
const NON_MUTATION_VALUES = new Set(['NOT_MUTATED']);
1672-
1673-
// flat extrat all the counts
1674-
const allCounts = _.chain(data)
1675-
.flatMap(item => item.counts ?? [])
1676-
.value();
1677-
1678-
const grouped = new Map<string, Set<string>>();
1679-
const lcValueToColor = _.keyBy(clinicalAttributeValues, d =>
1680-
d.value.toLowerCase()
1681-
);
1682-
1683-
// group them into mutated, not_mutated
1684-
for (const count of allCounts) {
1685-
const sampleIds: string[] = count.sampleIds ?? [];
1686-
if (
1687-
sampleIds.length === 0 ||
1688-
SKIP_VALUES.has(count.value)
1689-
)
1690-
continue; // case of no samples or non profile samples
1691-
1692-
const key = NON_MUTATION_VALUES.has(count.value)
1693-
? count.value
1694-
: 'MUTATED';
1695-
1696-
if (!grouped.has(key)) {
1697-
grouped.set(key, new Set());
1698-
}
1648+
onMobxPromise([this.selectedSamples], async () => {
1649+
const chartInfo = this._geneSpecificChartMap.get(
1650+
chartMeta.uniqueKey
1651+
);
1652+
if (!chartInfo || !this.hasFilteredSamples) return;
16991653

1700-
const sampleIdSet = grouped.get(key)!;
1701-
for (const rawId of sampleIds) {
1702-
const id = rawId.substring(
1703-
rawId.lastIndexOf('_') + 1
1704-
);
1705-
sampleIdSet.add(id);
1706-
}
1707-
}
1654+
const data: any[] = await invokeGenomicDataCountIncludeSampleid(
1655+
chartInfo,
1656+
this.filters,
1657+
true
1658+
);
17081659

1709-
const groups: SessionGroupData[] = _.chain(
1710-
clinicalAttributeValues
1711-
)
1712-
.map(attrVal => {
1713-
const groupName = attrVal.value;
1714-
const sampleIdSet = grouped.get(groupName);
1660+
const lcValueToColor = _.keyBy(clinicalAttributeValues, d =>
1661+
d.value.toLowerCase()
1662+
);
17151663

1716-
if (!sampleIdSet || sampleIdSet.size === 0) {
1717-
return null;
1718-
}
1664+
/**
1665+
* Step 1:
1666+
* Normalize counts into { group, sampleId } pairs
1667+
*/
1668+
const groupedSamples = groupSamplesByMutationStatus(data);
1669+
1670+
/**
1671+
* Step 2:
1672+
* Build SessionGroupData objects for each group, using the sample ids from step 1
1673+
*/
1674+
const groups: SessionGroupData[] = _.chain(
1675+
clinicalAttributeValues
1676+
)
1677+
.map(attrVal => {
1678+
const sampleIds = groupedSamples[attrVal.value];
1679+
if (!sampleIds?.length) return null;
17191680

1720-
const sampleIdentifiers: SampleIdentifier[] = Array.from(
1721-
sampleIdSet
1722-
).map(id => ({
1681+
return getGroupParameters(
1682+
attrVal.value,
1683+
sampleIds.map(id => ({
17231684
sampleId: id,
17241685
studyId: this.studyIds[0],
1725-
}));
1726-
const lcValue = attrVal.value.toLowerCase();
1686+
})),
1687+
this.studyIds,
1688+
lcValueToColor[attrVal.value.toLowerCase()]?.color
1689+
);
1690+
})
1691+
.compact()
1692+
.slice(
1693+
0,
1694+
doesChartHaveComparisonGroupsLimit(chartMeta)
1695+
? MAX_GROUPS_IN_SESSION
1696+
: undefined
1697+
)
1698+
.value();
17271699

1728-
return getGroupParameters(
1729-
groupName,
1730-
sampleIdentifiers,
1731-
this.studyIds,
1732-
lcValueToColor[lcValue].color
1733-
);
1734-
})
1735-
.filter((g): g is SessionGroupData => g !== null)
1736-
.slice(
1737-
0,
1738-
doesChartHaveComparisonGroupsLimit(chartMeta)
1739-
? MAX_GROUPS_IN_SESSION
1740-
: undefined
1741-
)
1742-
.value();
1700+
statusCallback(LoadingPhase.CREATING_SESSION);
17431701

1744-
statusCallback(LoadingPhase.CREATING_SESSION);
1702+
const { id } = await comparisonClient.addComparisonSession({
1703+
groups,
1704+
clinicalAttributeName: chartMeta.displayName,
1705+
origin: this.studyIds,
1706+
});
17451707

1746-
// create session and get id
1747-
const { id } = await comparisonClient.addComparisonSession({
1748-
groups,
1749-
clinicalAttributeName: chartMeta.displayName, // NOT SURE WHAT TO PASS HERE
1750-
origin: this.studyIds,
1751-
});
1752-
return resolve(id);
1753-
}
1754-
);
1708+
resolve(id);
1709+
});
17551710
});
17561711
}
17571712

@@ -2197,12 +2152,6 @@ export class StudyViewPageStore
21972152
params.clinicalAttributeValues!,
21982153
statusCallback
21992154
);
2200-
2201-
// comparisonId = await this.createCnaGeneComparisonSession(
2202-
// chartMeta,
2203-
// [chartInfo.hugoGeneSymbol],
2204-
// statusCallback
2205-
// );
22062155
} else {
22072156
comparisonId = await this.createCategoricalAttributeComparisonSession(
22082157
chartMeta,

src/pages/studyView/StudyViewUtils.spec.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
updateCustomIntervalFilter,
6767
updateGeneQuery,
6868
updateSavedUserPreferenceChartIds,
69+
groupSamplesByMutationStatus,
6970
} from 'pages/studyView/StudyViewUtils';
7071
import {
7172
CancerStudy,
@@ -3656,6 +3657,69 @@ describe('StudyViewUtils', () => {
36563657
});
36573658
});
36583659

3660+
describe('groupSamplesByMutationStatus', () => {
3661+
const data = [
3662+
{
3663+
counts: [
3664+
{
3665+
value: 'NOT_PROFILED',
3666+
sampleIds: ['ignore_me_001'],
3667+
},
3668+
],
3669+
},
3670+
{
3671+
counts: [
3672+
{
3673+
value: 'Missense_Mutation',
3674+
sampleIds: ['study_ABC_123', 'study_DEF_456'],
3675+
},
3676+
],
3677+
},
3678+
{
3679+
counts: [
3680+
{
3681+
value: 'NOT_MUTATED',
3682+
sampleIds: ['study_GHI_789'],
3683+
},
3684+
],
3685+
},
3686+
] as any;
3687+
3688+
it('groups and trims sample ids by mutation status correctly', () => {
3689+
const result = groupSamplesByMutationStatus(data);
3690+
assert.deepEqual(result, {
3691+
MUTATED: ['123', '456'],
3692+
NOT_MUTATED: ['789'],
3693+
});
3694+
});
3695+
3696+
it('handles missing or empty counts safely', () => {
3697+
const emptyData = [{}, { counts: [] }];
3698+
expect(groupSamplesByMutationStatus(emptyData)).toEqual({});
3699+
});
3700+
3701+
it('deduplicates sample IDs across mutation entries', () => {
3702+
const duplicateData = [
3703+
{
3704+
counts: [
3705+
{
3706+
value: 'Missense_Mutation',
3707+
sampleIds: ['study_X_001', 'study_X_002'],
3708+
},
3709+
{
3710+
value: 'Nonsense_Mutation',
3711+
sampleIds: ['study_X_001'],
3712+
},
3713+
],
3714+
},
3715+
];
3716+
3717+
expect(groupSamplesByMutationStatus(duplicateData)).toEqual({
3718+
MUTATED: ['001', '002'],
3719+
});
3720+
});
3721+
});
3722+
36593723
describe('getFilteredStudiesWithSamples', () => {
36603724
const samples: Sample[] = [
36613725
{

src/pages/studyView/StudyViewUtils.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4280,6 +4280,28 @@ export async function invokeGenomicDataCountIncludeSampleid(
42804280
return result;
42814281
}
42824282

4283+
export function groupSamplesByMutationStatus(data: any[]) {
4284+
const SKIP_VALUES = new Set(['NOT_PROFILED']);
4285+
const NON_MUTATION_VALUES = new Set(['NOT_MUTATED']);
4286+
4287+
return _.chain(data)
4288+
.flatMap(d => d.counts ?? [])
4289+
.filter(c => !SKIP_VALUES.has(c.value) && c.sampleIds.length > 0)
4290+
.flatMap(c => {
4291+
const group = NON_MUTATION_VALUES.has(c.value)
4292+
? c.value
4293+
: 'MUTATED';
4294+
4295+
return c.sampleIds.map((rawId: string) => ({
4296+
group,
4297+
sampleId: rawId.substring(rawId.lastIndexOf('_') + 1),
4298+
}));
4299+
})
4300+
.groupBy('group')
4301+
.mapValues(items => _.uniq(items.map(i => i.sampleId)))
4302+
.value();
4303+
}
4304+
42834305
export async function invokeGenomicDataCount(
42844306
chartInfo: GenomicChart,
42854307
filters: StudyViewFilter

0 commit comments

Comments
 (0)