Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default [

/* ...then apply your custom tweaks */
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-explicit-any': 'error',

/* Security: flag unsafe DOM manipulation */
'no-unsanitized/method': 'error',
Expand Down
138 changes: 52 additions & 86 deletions src/__spec__/filter-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,141 +5,107 @@ import {
getFilteredVariants,
VariantsForFilter,
} from '../filter-config';
import { TransformedVariant } from '../adapters/variation-adapter';

const transformedVariantPositions = [
const makeVariant = (overrides: Partial<TransformedVariant>): TransformedVariant =>
({
accession: 'A',
variant: 'A',
start: 1,
xrefNames: [],
hasPredictions: false,
consequenceType: 'missense',
type: 'VARIANT',
begin: '1',
end: '1',
xrefs: [],
cytogeneticBand: '',
locations: [],
somaticStatus: 0,
sourceType: 'uniprot',
wildType: 'A',
...overrides,
} as TransformedVariant);

const transformedVariantPositions: VariantsForFilter = [
{
variants: [
{
makeVariant({
accession: 'A',
begin: 1,
end: 1,
begin: '1',
end: '1',
start: 1,
tooltipContent: '',
sourceType: 'source',
variant: 'V',
protvistaFeatureId: 'id1',
xrefNames: [],
type: 'VARIANT',
wildType: 'A',
alternativeSequence: 'V',
consequenceType: 'disease',
clinicalSignificances: [
{
type: 'Variant of uncertain significance',
sources: ['Ensembl'],
type: 'Variant of uncertain significance' as never,
sources: [],
},
],
xrefs: [],
hasPredictions: false,
},
{
}),
makeVariant({
accession: 'B',
begin: 1,
end: 1,
begin: '1',
end: '1',
start: 1,
tooltipContent: '',
sourceType: 'source',
variant: 'D',
protvistaFeatureId: 'id2',
xrefNames: [],
type: 'VARIANT',
wildType: 'A',
alternativeSequence: 'D',
consequenceType: 'disease',
xrefs: [],
hasPredictions: false,
},
}),
],
},
{
variants: [
{
makeVariant({
accession: 'C',
begin: 2,
end: 2,
begin: '2',
end: '2',
start: 2,
tooltipContent: '',
sourceType: 'source',
variant: 'V',
protvistaFeatureId: 'id1',
xrefNames: [],
type: 'VARIANT',
wildType: 'A',
alternativeSequence: 'V',
consequenceType: 'disease',
xrefs: [],
hasPredictions: false,
},
}),
],
},
{
variants: [
{
makeVariant({
accession: 'D',
begin: 3,
end: 3,
begin: '3',
end: '3',
start: 3,
tooltipContent: '',
sourceType: 'source',
variant: 'V',
protvistaFeatureId: 'id1',
xrefNames: [],
type: 'VARIANT',
wildType: 'A',
alternativeSequence: 'V',
consequenceType: 'disease',
siftScore: 0.5,
xrefs: [],
hasPredictions: false,
},
}),
],
},
];

describe('Variation filter config', () => {
test('it should filter according to the callback function', () => {
const filteredVariants = getFilteredVariants(
transformedVariantPositions as VariantsForFilter,
transformedVariantPositions,
(variant) => variant.accession === 'A'
);
expect(filteredVariants).toEqual([
{
variants: [transformedVariantPositions[0].variants[0]],
},
{
variants: [],
},
{
variants: [],
},
{ variants: [transformedVariantPositions[0].variants[0]] },
{ variants: [] },
{ variants: [] },
]);
});

test('it should get the right colour for disease', () => {
const firstVariant = colorConfig(
transformedVariantPositions[0].variants[0]
);
expect(firstVariant).toEqual('#009e73');
const result = colorConfig(transformedVariantPositions[0].variants[0]);
expect(result).toEqual('#009e73');
});

test('it should get the right colour for non disease', () => {
const secondVariant = colorConfig(
transformedVariantPositions[0].variants[1]
);
expect(secondVariant).toEqual('#009e73');
const result = colorConfig(transformedVariantPositions[0].variants[1]);
expect(result).toEqual('#009e73');
});

test('it should get the right colour for other', () => {
const thirdVariant = colorConfig(
transformedVariantPositions?.[1].variants[0]
);
expect(thirdVariant).toEqual('#009e73');
const result = colorConfig(transformedVariantPositions[1].variants[0]);
expect(result).toEqual('#009e73');
});

test('it should get the right colour for predicted', () => {
const thirdVariant = colorConfig(
transformedVariantPositions[2].variants[0]
);
expect(thirdVariant).toEqual('#009e73');
const result = colorConfig(transformedVariantPositions[2].variants[0]);
expect(result).toEqual('#009e73');
});
});
3 changes: 2 additions & 1 deletion src/adapters/alphafold-confidence-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ const getConfidenceURLFromPayload = (af: AlphaFoldPayload[number]) =>

const loadConfidence = async (
url: string
): Promise<AlphafoldConfidencePayload> => {
): Promise<AlphafoldConfidencePayload | undefined> => {
try {
const payload = await fetch(url);
return payload.json();
} catch (e) {
console.error('Could not load AlphaFold confidence', e);
return undefined;
}
};

Expand Down
24 changes: 16 additions & 8 deletions src/adapters/alphamissense-heatmap-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import {
rowSplitter,
} from './alphamissense-pathogenicity-adapter';

const parseCSV = (rawText: string): Array<Record<string, string>> => {
const data = [];
type HeatmapRow = {
xValue: number;
yValue: string;
score: number;
};

const parseCSV = (rawText: string): HeatmapRow[] => {
const data: HeatmapRow[] = [];

for (const [i, row] of rawText.split(rowSplitter).entries()) {
if (i === 0 || !row) {
continue;
}
const [, , positionString, mutated, pathogenicityScore] =
row.match(cellSplitter);
const match = row.match(cellSplitter);
if (!match) continue;
const [, , positionString, mutated, pathogenicityScore] = match;

data.push({
xValue: +positionString,
Expand All @@ -27,13 +34,14 @@ const parseCSV = (rawText: string): Array<Record<string, string>> => {
// Load and parse
const loadAndParseAnnotations = async (
url: string
): Promise<Array<Record<string, string>>> => {
): Promise<HeatmapRow[] | undefined> => {
try {
const payload = await fetch(url);
const rawCSV = await payload.text();
return parseCSV(rawCSV);
} catch (e) {
console.error('Could not load AlphaMissense pathogenicity', e);
return undefined;
}
};

Expand All @@ -52,9 +60,9 @@ const transformData = async (
protein.sequence.sequence === sequence && amAnnotationsUrl
);
if (alphaFoldSequenceMatch.length === 1) {
const heatmapData = await loadAndParseAnnotations(
alphaFoldSequenceMatch[0].amAnnotationsUrl
);
const url = alphaFoldSequenceMatch[0].amAnnotationsUrl;
if (!url) return undefined;
const heatmapData = await loadAndParseAnnotations(url);
return heatmapData;
} else if (alphaFoldSequenceMatch.length > 1) {
console.warn(
Expand Down
11 changes: 6 additions & 5 deletions src/adapters/alphamissense-pathogenicity-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const pathogenicityCategories = [
{ min: pathogenic, max: certainlyPathogenic, code: 'P' },
];

const getPathogenicityCode = (score) => {
const getPathogenicityCode = (score: number): string | undefined => {
for (const { min, max, code } of pathogenicityCategories) {
if (score >= min && score < max) {
return code;
Expand Down Expand Up @@ -93,13 +93,14 @@ const parseCSV = (rawText: string): string => {
};

// Load and parse
const loadAndParseAnnotations = async (url: string): Promise<string> => {
const loadAndParseAnnotations = async (url: string): Promise<string | undefined> => {
try {
const payload = await fetch(url);
const rawCSV = await payload.text();
return parseCSV(rawCSV);
} catch (e) {
console.error('Could not load AlphaMissense pathogenicity score', e);
return undefined;
}
};

Expand All @@ -118,9 +119,9 @@ const transformData = async (
protein.sequence.sequence === sequence && amAnnotationsUrl
);
if (alphaFoldSequenceMatch.length === 1) {
const heatmapData = await loadAndParseAnnotations(
alphaFoldSequenceMatch[0].amAnnotationsUrl
);
const url = alphaFoldSequenceMatch[0].amAnnotationsUrl;
if (!url) return undefined;
const heatmapData = await loadAndParseAnnotations(url);
return heatmapData;
} else if (alphaFoldSequenceMatch.length > 1) {
console.warn(
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/feature-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { renameProperties } from '../utils';
import formatTooltip from '../tooltips/feature-tooltip';

const transformData = (data) => {
let transformedData = [];
const transformData = (data: { features?: Record<string, unknown>[] }) => {
let transformedData: Record<string, unknown>[] = [];
const { features } = data;
if (features && features.length > 0) {
transformedData = features.map((feature) => {
Expand Down
24 changes: 17 additions & 7 deletions src/adapters/proteomics-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { renameProperties } from '../utils';
import formatTooltip from '../tooltips/feature-tooltip';
import formatTooltip, { TooltipFeature } from '../tooltips/feature-tooltip';

const proteomicsTrackProperties = (feature, taxId) => {
type ProteomicsFeature = TooltipFeature & {
unique: boolean;
ptms?: { name: string; position: number; sources: string[]; dbReferences: { id: string; properties: Record<string, string> }[] }[];
};

type ProteomicsData = {
features: ProteomicsFeature[];
taxid: number;
};

const proteomicsTrackProperties = (feature: ProteomicsFeature, taxId: number) => {
return {
category: 'PROTEOMICS',
type: feature.unique ? 'unique' : 'non_unique',
tooltipContent: formatTooltip(feature, taxId),
tooltipContent: formatTooltip(feature, String(taxId)),
};
};

const transformData = (data) => {
let adaptedData = [];
const transformData = (data: ProteomicsData) => {
let adaptedData: (ProteomicsFeature & { start?: number })[] = [];

if (data && data.length !== 0) {
if (data && data.features && data.features.length !== 0) {
adaptedData = data.features.map((feature) => {
feature.residuesToHighlight = feature.ptms?.map((ptm) => ({
name: ptm.name,
Expand All @@ -26,7 +36,7 @@ const transformData = (data) => {
);
});

adaptedData = renameProperties(adaptedData);
adaptedData = renameProperties(adaptedData) as typeof adaptedData;
}
return adaptedData;
};
Expand Down
7 changes: 5 additions & 2 deletions src/adapters/ptm-exchange-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,13 @@ const convertPtmExchangePtms = (
`MOD_RES_LS ${absolutePosition}-${absolutePosition}`,
groupedPtms,
aa,
confidenceScore
confidenceScore ?? ''
),
color:
(confidenceScore && ConfidenceScoreColors[confidenceScore]) || 'black',
(confidenceScore !== null &&
confidenceScore in ConfidenceScoreColors &&
ConfidenceScoreColors[confidenceScore as keyof typeof ConfidenceScoreColors]) ||
'black',
};
});
};
Expand Down
Loading