Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ type GeneType = {
kbStatementRelated: boolean;
drugTargetable: boolean;
expressionVariants?: ExpOutliersType;
copyVariants?: CopyNumberType;
knownFusionPartner: boolean;
knownSmallMutation: boolean;
name: string;
Expand Down
18 changes: 18 additions & 0 deletions app/components/PrintTable/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { ColDef, ValueGetterParams } from '@ag-grid-community/core';

const getNestedValue = (row: Record<string, unknown>, path: string) => path
.split('.')
.reduce<unknown>((currentValue, key) => {
if (currentValue === null || currentValue === undefined) {
return undefined;
}
if (typeof currentValue !== 'object') {
return undefined;
}
return (currentValue as Record<string, unknown>)[key];
}, row);

// Resolves the displayed value of a cell for a given row + colDef, applying
// the colDef's valueGetter when present and falling back to row[colId|field].
// Shared by PrintTable's row-rendering / collapse-key logic and any caller
Expand All @@ -13,5 +25,11 @@ export const resolveCellValue = <T extends Record<string, unknown>>(
return colDef.valueGetter({ data: row } as ValueGetterParams);
}
const key = (colDef.field ?? colDef.colId) as string;
if (!key) {
return undefined;
}
if (key.includes('.')) {
return getNestedValue(row, key);
Comment thread
kttkjl marked this conversation as resolved.
}
return row[key];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import {
render,
screen,
waitFor,
} from '@testing-library/react';

import GenomicAlterationsTable from '..';

const mockDataTable = jest.fn();
const mockPrintTable = jest.fn();

jest.mock('@/components/DataTable', () => (props) => {
mockDataTable(props);
return <div data-testid="data-table">{props.titleText || 'data-table'}</div>;
});

jest.mock('@/components/PrintTable', () => (props) => {
mockPrintTable(props);
return <div data-testid="print-table">print-table</div>;
});

describe('GenomicAlterationsTable', () => {
beforeEach(() => {
mockDataTable.mockClear();
mockPrintTable.mockClear();
});

test('renders DataTable for smallMutation in non-print mode', async () => {
render(
<GenomicAlterationsTable
variantCategory="smallMutation"
variantData={[{ ident: 'v1', gene: { name: 'TP53' } } as any]}
isPrint={false}
/>,
);

expect(await screen.findByTestId('data-table')).toBeInTheDocument();
expect(screen.getByText('Small Mutations')).toBeInTheDocument();

expect(mockDataTable).toHaveBeenCalled();
const dataTableProps = mockDataTable.mock.calls[0][0];
expect(dataTableProps.isPrint).toBe(false);
expect(dataTableProps.canExport).toBe(true);
expect(dataTableProps.titleText).toBe('Small Mutations');

expect(mockPrintTable).not.toHaveBeenCalled();
});

test('renders PrintTable for smallMutation in print mode', async () => {
render(
<GenomicAlterationsTable
variantCategory="smallMutation"
variantData={[{ ident: 'v1', gene: { name: 'TP53' } } as any]}
isPrint
/>,
);

expect(await screen.findByTestId('print-table')).toBeInTheDocument();
expect(screen.getByText('Small Mutations')).toBeInTheDocument();

expect(mockPrintTable).toHaveBeenCalled();
expect(mockDataTable).not.toHaveBeenCalled();
});

test('uses flattened expression print columns and excludes Actions', async () => {
render(
<GenomicAlterationsTable
variantCategory="expression"
variantData={[{ ident: 'v1', gene: { name: 'EGFR', copyVariants: { cnvState: 'gain' } } } as any]}
isPrint
/>,
);

await screen.findByTestId('print-table');

const printTableProps = mockPrintTable.mock.calls[0][0];
const printHeaders = printTableProps.columnDefs.map((col) => col.headerName);

expect(printHeaders).toContain('Gene');
expect(printHeaders).toContain('Expression Class');
expect(printHeaders).toContain('Disease Perc');
expect(printHeaders).toContain('Disease Z-Score');
expect(printHeaders).not.toContain('Actions');
expect(printTableProps.fullWidth).toBe(true);
});

test('shows loader and no tables for unknown variantCategory', async () => {
render(
<GenomicAlterationsTable
variantCategory="unknown"
variantData={[] as any}
isPrint={false}
/>,
);

await waitFor(() => {
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});

expect(mockDataTable).not.toHaveBeenCalled();
expect(mockPrintTable).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { ColDef, ColGroupDef } from '@ag-grid-community/core';
import { createGeneRelatedValueGetter } from '@/views/ReportView/components/StructuralVariants/columnDefs';

const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });

const smallMutationsColumnDefs: ColDef[] = [{
headerName: 'Gene',
field: 'gene.name',
cellRenderer: 'GeneCellRenderer',
cellRendererParams: { link: true },
},
{
headerName: 'Protein Change',
field: 'proteinChange',
},
{
headerName: 'Transcript',
field: 'transcript',
},
{
headerName: 'Location',
field: 'location',
valueGetter: ({ data }) => data.chromosome && `${data.chromosome}:${data.startPosition}${data.endPosition && data.startPosition !== data.endPosition
? `-${data.endPosition}`
: ''
}`,
},
{
headerName: 'Zygosity',
field: 'zygosity',
},
{
headerName: 'VAF %',
colId: 'tumourAltCount/tumourDepth',
field: 'tumourAltCount/tumourDepth',
valueGetter: ({
data: {
tumourAltCount, tumourDepth, rnaAltCount, rnaDepth,
},
}) => {
if ((tumourAltCount && tumourDepth) || (tumourAltCount === 0 || tumourDepth === 0)) {
return ((tumourAltCount / tumourDepth) * 100).toFixed(0);
}
if ((rnaAltCount && rnaDepth) || (rnaAltCount === 0 || rnaDepth === 0)) {
return 'N/A (RNA)';
}
return '';
},
comparator: collator.compare,
},
{
headerName: 'Actions',
Comment thread
bnguyen-bcgsc marked this conversation as resolved.
Outdated
colId: 'actions',
cellRenderer: 'ActionCellRenderer',
pinned: 'right',
sortable: false,
suppressMenu: true,
}];

const copyNumberColumnDefs: ColDef[] = [{
headerName: 'Gene',
cellRenderer: 'GeneCellRenderer',
cellRendererParams: { link: true },
field: 'gene.name',
},
{
headerName: 'Copy Change',
field: 'copyChange',
valueFormatter: (params) => {
if (params.value === null || params.value === undefined) return '';

const num = Number(params.value);

// If the number is greater than zero, prepend the "+" sign
if (num > 0) {
return `+${num}`;
}
// Zero or negative numbers will naturally format with their own sign or nothing
return num.toString();
},
},
{
headerName: 'CNV State',
field: 'cnvState',
},
{
headerName: 'Chr:band',
field: 'chromosomeBand',
},
{
headerName: 'Actions',
Comment thread
bnguyen-bcgsc marked this conversation as resolved.
Outdated
colId: 'actions',
cellRenderer: 'ActionCellRenderer',
pinned: 'right',
sortable: false,
suppressMenu: true,
}];

const structuralVariantsColumnDefs = [{
headerName: 'Genes 5`::3`',
colId: 'genes',
cellRenderer: 'GeneCellRenderer',
cellRendererParams: { link: true },
valueGetter: createGeneRelatedValueGetter('name', ' :: '),
},
{
headerName: 'Exons 5`/3`',
colId: 'exons',
valueGetter: (params) => (params.data.exon1 && params.data.exon2
? `${params.data.exon1}:${params.data.exon2}`
: (params.data.exon1 || params.data.exon2)),
},
{
headerName: 'Breakpoint',
colId: 'breakpoint',
field: 'breakpoint',
},
{
headerName: 'Event Type',
colId: 'eventType',
field: 'eventType',
},
{
headerName: 'Sample',
colId: 'detectedIn',
field: 'detectedIn',
},
{
headerName: 'Cytogenic Description',
colId: 'conventionalName',
field: 'conventionalName',
},
{
headerName: 'Actions',
Comment thread
bnguyen-bcgsc marked this conversation as resolved.
Outdated
cellRenderer: 'ActionCellRenderer',
colId: 'actions',
pinned: 'right',
sortable: false,
suppressMenu: true,
}];

const expressionColumnDefs: Array<ColDef | ColGroupDef> = [{
headerName: 'Gene',
field: 'gene.name',
cellRenderer: 'GeneCellRenderer',
cellRendererParams: { link: true },
},
{
headerName: 'Expression Class',
field: 'expressionState',
},
{
headerName: 'Disease',
children: [
{ headerName: 'Perc', field: 'diseasePercentile' },
{ headerName: 'Z-Score', field: 'diseaseZScore' },
],
},
{
headerName: 'Actions',
Comment thread
bnguyen-bcgsc marked this conversation as resolved.
Outdated
cellRenderer: 'ActionCellRenderer',
pinned: 'right',
colId: 'actions',
sortable: false,
suppressMenu: true,
}];

export {
smallMutationsColumnDefs,
copyNumberColumnDefs,
expressionColumnDefs,
structuralVariantsColumnDefs,
};
Loading
Loading