From a3355f2cbda6eb4e00a740dd81c87b932dae656d Mon Sep 17 00:00:00 2001 From: apolignano Date: Thu, 23 Jan 2025 13:43:47 +0000 Subject: [PATCH 1/8] IPScan ProtViewer reorganization, mobidblite and residues restructuring --- ...nts+nightingale-interpro-track+5.0.0.patch | 6 +- src/components/IPScan/Summary/serializers.ts | 10 +- .../DomainsOnProteinLoaded/index.tsx | 305 ++++++++---------- .../Related/DomainsOnProtein/index.tsx | 282 +++++++++++----- 4 files changed, 348 insertions(+), 255 deletions(-) diff --git a/patches/@nightingale-elements+nightingale-interpro-track+5.0.0.patch b/patches/@nightingale-elements+nightingale-interpro-track+5.0.0.patch index 0cf5218cd..79506eb5f 100644 --- a/patches/@nightingale-elements+nightingale-interpro-track+5.0.0.patch +++ b/patches/@nightingale-elements+nightingale-interpro-track+5.0.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@nightingale-elements/nightingale-interpro-track/src/InterproEntryLayout.ts b/node_modules/@nightingale-elements/nightingale-interpro-track/src/InterproEntryLayout.ts -index 8302fc6..ac45de6 100644 +index 8302fc6..7f604b1 100644 --- a/node_modules/@nightingale-elements/nightingale-interpro-track/src/InterproEntryLayout.ts +++ b/node_modules/@nightingale-elements/nightingale-interpro-track/src/InterproEntryLayout.ts @@ -39,24 +39,30 @@ export default class InterproEntryLayout extends DefaultLayout { @@ -11,7 +11,7 @@ index 8302fc6..ac45de6 100644 constructor({ expanded, padding, -+ samesize, ++ samesize, ...otherOptions - }: LayoutOptions & { expanded: boolean; padding: number }) { + }: LayoutOptions & { expanded: boolean; padding: number, samesize?: boolean }) { @@ -77,7 +77,7 @@ index 8302fc6..ac45de6 100644 ) { + + let childHeight = this.samesize ? EXPANDED_HEIGHT : CHILD_HEIGHT -+ ++ for (let i = 0; i < residues.length; i++) { const resGroup = residues[i]; InterproEntryLayout.filterOutResidueFragmentsOutOfLocation( diff --git a/src/components/IPScan/Summary/serializers.ts b/src/components/IPScan/Summary/serializers.ts index 3629e5df7..44f358b6c 100644 --- a/src/components/IPScan/Summary/serializers.ts +++ b/src/components/IPScan/Summary/serializers.ts @@ -1,8 +1,12 @@ import { NOT_MEMBER_DBS } from 'menuConfig'; import { iproscan2urlDB } from 'utils/url-patterns'; +import { + proteinViewerReorganization, + sectionsReorganization, +} from 'components/Related/DomainsOnProtein'; -const OTHER_FEATURES_DBS = ['funfam']; -const OTHER_RESIDUES_DBS = ['pirsr']; +const OTHER_FEATURES_DBS = ['']; +const OTHER_RESIDUES_DBS = ['']; type IpScanEntry = { accession: string; @@ -212,7 +216,7 @@ export const mergeData = ( if (residues[0] && residues[0].locations.length !== 0) mergedData.other_residues.push(residues[0]); } else { - unintegrated[mergedMatch.accession] = mergedMatch; + // unintegrated[mergedMatch.accession] = mergedMatch; } const representativeLocations = processedMatch.locations.filter( diff --git a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx index 14c8ac8e3..0de20e661 100644 --- a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx +++ b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx @@ -1,13 +1,17 @@ import React, { PropsWithChildren } from 'react'; import { addConfidenceTrack } from 'components/Structure/ViewerAndEntries/ProteinViewerForAlphafold'; import loadable from 'higherOrder/loadable'; -import { groupByEntryType } from 'components/Related/DomainsOnProtein'; +import { + groupByEntryType, + sectionsReorganization, +} from 'components/Related/DomainsOnProtein'; import { ProteinsAPIVariation } from '@nightingale-elements/nightingale-variation/dist/proteinAPI'; import { ExtendedFeature, ExtendedFeatureLocation, } from 'components/ProteinViewer'; -import { sleep } from 'timing-functions'; +import { proteinViewerReorganization } from 'components/Related/DomainsOnProtein'; +import { FeatureLocation } from 'node_modules/@nightingale-elements/nightingale-track/dist'; const ProteinViewer = loadable({ loader: () => @@ -73,10 +77,14 @@ function getBoundaries(item: ExtendedFeature | ExtendedFeature[]) { let accession = undefined; if (Array.isArray(item)) { - fragment = item[0].entry_protein_locations?.[0].fragments?.[0]; + if (item[0].entry_protein_locations) + fragment = item[0].entry_protein_locations?.[0].fragments?.[0]; + else fragment = item[0].locations?.[0].fragments?.[0]; accession = item[0].accession; } else { - fragment = item.entry_protein_locations?.[0].fragments?.[0]; + if (item.entry_protein_locations) + fragment = item.entry_protein_locations?.[0].fragments?.[0]; + else fragment = item.locations?.[0].fragments?.[0]; accession = item.accession; } if (fragment && accession) { @@ -105,18 +113,71 @@ export function sortTracks( return 0; } -const getMemberDBMatches = ( - interpro: Array, -): Array => { - const dbMatches: Array = []; - interpro.forEach((entry) => { - if (entry.children) { - entry.children.forEach((memberDBMatch) => { - dbMatches.push(memberDBMatch); - }); - } +const standardizeResidueStructure = ( + residues: Array, +): Array => { + const newResidues: Array = []; + residues.map((residueParentObj) => { + const tempResidue: ExtendedFeature = residueParentObj; + tempResidue.type = 'residue'; + tempResidue.locations = residueParentObj.residues?.[0].locations; + newResidues.push(tempResidue); + }); + return newResidues; +}; + +const standardizeMobiDBFeatureStructure = ( + features: Array, +): Array => { + const newFeatures: Array = []; + features.map((feature) => { + const tempFeature = { ...feature }; + const slicedTempFeatureLocations: Array = []; + tempFeature.accession = 'Mobidblt-Consensus Disorder Prediction'; + tempFeature.source_database = 'mobidblt'; + tempFeature.protein = ''; + tempFeature.locations?.map( + ( + location: ExtendedFeatureLocation & { + 'sequence-feature'?: string; + start?: number; + end?: number; + }, + idx: number, + ) => { + if ( + location['sequence-feature'] && + location['sequence-feature'] !== '' + ) { + if (location.start && location.end) { + const restructuredLocation: ExtendedFeatureLocation[] = [ + { + fragments: [ + { + start: location.start, + end: location.end, + seq_feature: location['sequence-feature'], + }, + ], + }, + ]; + + const tempChild: ExtendedFeature = { + accession: location['sequence-feature'], + source_database: 'mobidblt', + locations: restructuredLocation, + }; + tempFeature.children?.push(tempChild); + } + } else { + slicedTempFeatureLocations.push(location); + } + }, + ); + tempFeature.locations = slicedTempFeatureLocations; + newFeatures.push(tempFeature); }); - return dbMatches; + return newFeatures; }; export const makeTracks = ({ @@ -132,42 +193,7 @@ export const makeTracks = ({ 2. Merge unintegrated with result from (1.); 3. Sort matches in tracks based on their position but, if integrated, maintaining grouping for the same InterPro entry. - - // 1. and 2. - const integratedMatches = getMemberDBMatches(interpro); - const allMatches = integratedMatches.concat(unintegrated); - - // this was - const groups = groupByEntryType( - allMatches as { accession: string; type: string }[], - ); - - /* 3. - Group matches of the same type (e.g domain) by IntePro accession - sort matches by position within the same group, - sort all the groups based on first fragment of group. - - Object.keys(groups).map((key) => { - const uniqueInterproAccessions = [ - ...new Set(groups[key].map((match: ExtendedFeature) => match.integrated)), - ]; - const allMatchesGroupedByEntry = []; - - for (let i = 0; i < uniqueInterproAccessions.length; i++) { - const groupedEntry = groups[key].filter( - (match: ExtendedFeature) => - match.integrated == uniqueInterproAccessions[i], - ); - // Sort non-integrated and those appearing just once for an Interpro accession, independently from the grouped ones - if (uniqueInterproAccessions[i] === null || groupedEntry.length == 1) { - groupedEntry.map((entry) => allMatchesGroupedByEntry.push(entry)); - } else { - allMatchesGroupedByEntry.push(groupedEntry.sort(sortTracks)); - } - } - groups[key] = allMatchesGroupedByEntry.sort(sortTracks).flat(); - });*/ - + */ const groups = groupByEntryType( interpro.concat(unintegrated as { accession: string; type: string }[]), ); @@ -175,27 +201,7 @@ export const makeTracks = ({ // Merge domain and families into respective representative ones. Merge homologous superfamily into domains. const mergedData: ProteinViewerDataObject = groups; - // Domain and family as empty objects, to cancat other object later - if (!mergedData.domain) { - mergedData.domain = []; - } - - if (!mergedData.family) { - mergedData.family = []; - } - - // Add repeats and homologous superfamilies to domain - if (mergedData.homologous_superfamily) { - mergedData.domain = mergedData.domain.concat( - mergedData.homologous_superfamily, - ); - mergedData.homologous_superfamily = []; - } - - if (mergedData.repeat) { - mergedData.domain = mergedData.domain.concat(mergedData.repeat); - mergedData.repeat = []; - } + sectionsReorganization(mergedData); // Add representative data if (representativeFamilies?.length) @@ -277,6 +283,7 @@ type Props = PropsWithChildren<{ dataConfidence?: RequestedData; dataVariation?: RequestedData; dataProteomics?: RequestedData; + dataFeatures?: RequestedData; conservationError?: string | null; showConservationButton?: boolean; handleConservationLoad?: () => void; @@ -290,6 +297,7 @@ const DomainsOnProteinLoaded = ({ dataConfidence, dataVariation, dataProteomics, + dataFeatures, conservationError, showConservationButton, handleConservationLoad, @@ -301,10 +309,11 @@ const DomainsOnProteinLoaded = ({ (mainData as ProteinEntryPayload).metadata || (mainData as { payload: ProteinEntryPayload }).payload.metadata; - /* - Special tracks are now added to the dataMerged object before being sorted based on FIRST_IN_ORDER. - Adding the tracks to the dataSorted object, caused the Alphafold track and variants track to be displayed always at the first/last position. - */ + let mainTracks: string[] = []; + let hideCategories: Record = {}; + const renamedTracks = ['domain', 'family', 'residues']; + let flattenedData = undefined; + if (dataConfidence) addConfidenceTrack(dataConfidence, protein.accession, dataMerged); @@ -324,77 +333,44 @@ const DomainsOnProteinLoaded = ({ } } - const uniqueResidues: Record = {}; - - // Group PIRSR residue by description and position - let pirsrFound = false; - for (let i = 0; i < dataMerged.residues?.length; i++) { - const currentResidue = dataMerged.residues[i] as ExtendedFeature; - if (currentResidue.source_database === 'pirsr') { - if (!pirsrFound) pirsrFound = true; - const residueStart = - currentResidue.locations?.[0].fragments?.[0].start || 0; - const residueEnd = currentResidue.locations?.[0].fragments?.[0].end || 0; - const residueDescription = - currentResidue.locations?.[0].description?.replace('.', ''); - - const dictKey = - residueStart.toString() + residueEnd.toString() + residueDescription; - - if (!uniqueResidues[dictKey]) uniqueResidues[dictKey] = currentResidue; - } else { - uniqueResidues[currentResidue.accession] = currentResidue; + // Results coming from InterProScan need a different processing pipeline. The data coming in is in a different format + // and the ProteinViewer components are used in a different way in the InterproScan results section. + if (protein.accession.startsWith('iprscan')) { + // What happens in the DomainsOnProtein component for matches coming from elasticsearch is skipped for the + // InterProScan results section, because the DomainsOnProteinLoaded is used right away. + // Executing those steps here below. KEEP THIS ORDER OF OPERATIONS + + // Residues' structure needs to change to allow PIRSR grouping and correct display on the PV + if (dataMerged['conserved_residues']) { + dataMerged['conserved_residues'] = standardizeResidueStructure( + dataMerged['conserved_residues'] as ExtendedFeature[], + ); } - } - // Create fake PIRSR object to display group label - if (pirsrFound) - uniqueResidues['PIRSR'] = { - accession: 'PIRSR_GROUP', - source_database: 'pirsr', - type: 'residue', - locations: [ - { - description: 'PIRSR', - fragments: [{ residues: '', start: -10, end: 0 }], - } as ExtendedFeatureLocation, - ], - }; + proteinViewerReorganization(dataFeatures, dataMerged); + sectionsReorganization(dataMerged); - dataMerged.conserved_residues = Object.values(uniqueResidues).sort((a, b) => { - // If comparing two entries from different DBs, put the non-pirsr always first (a) OR if source database is pirsr and first element is fake label, put fake label first - if ( - (a.source_database !== 'pirsr' && b.source_database === 'pirsr') || - (a.source_database === b.source_database && a.accession === 'PIRSR_GROUP') - ) - return -1; - // If comparing two entries from different DBs, put the non-pirsr always first (b) OR if source database is pirsr and second element is fake label, put fake label first - else if ( - (a.source_database === 'pirsr' && b.source_database !== 'pirsr') || - (a.source_database === b.source_database && b.accession === 'PIRSR_GROUP') - ) - return 1; - // All other cases - else return a.accession.localeCompare(b.accession); - }); + if (dataMerged['intrinsically_disordered_regions']) { + dataMerged['intrinsically_disordered_regions'] = + standardizeMobiDBFeatureStructure( + dataMerged['intrinsically_disordered_regions'] as ExtendedFeature[], + ); + } - if (dataMerged.domain) dataMerged.domains = dataMerged.domain.slice(); + // Sort data by match position, but exclude PIRSR, which is sorted in proteinViewerReorganization + Object.entries(dataMerged as ProteinViewerDataObject).map( + (group) => { + if (group[0] !== 'conserved_residues') group[1].sort(sortTracks).flat(); + }, + ); - if (dataMerged.family) dataMerged.families = dataMerged.family.slice(); + // Handle MobiDB-lite different format - const renamedTracks = ['domain', 'family', 'residues']; - const sortedData = flattenTracksObject(dataMerged).filter( - (track) => !renamedTracks.includes(track[0]), - ); - - let mainTracks: string[] = []; - let hideCategories: Record = {}; + flattenedData = flattenTracksObject(dataMerged).filter( + (track) => !renamedTracks.includes(track[0]), + ); - if (protein.accession.startsWith('iprscan')) { - const homologous_superfamily = sortedData.filter( - (entry) => entry[0] == 'homologous superfamily', - )[0]; - const representative_domains = sortedData.filter( + const representative_domains = flattenedData.filter( (entry) => entry[0] == 'representative domains', )[0]; @@ -406,17 +382,18 @@ const DomainsOnProteinLoaded = ({ }); } - sortedData.map((entry) => { + flattenedData.map((entry) => { if (entry[0] === 'domains') { - if (homologous_superfamily) { - entry[1] = entry[1].concat(homologous_superfamily[1]); - } - if (representative_domains) { entry[1] = entry[1].concat(representative_domains[1]); } + } else if (entry[0] === 'other features') { + entry[1] = []; + } else if (entry[0] === 'representative domains') { + entry[1] = []; } }); + // End of skipped reorganization steps mainTracks = [ 'alphafold confidence', @@ -446,10 +423,6 @@ const DomainsOnProteinLoaded = ({ 'pfam-n': false, funfam: false, }; - - sortedData.map((entry) => { - (entry[1] as ExtendedFeature[]).sort(sortTracks).flat(); - }); } else { mainTracks = [ 'alphafold confidence', @@ -476,23 +449,29 @@ const DomainsOnProteinLoaded = ({ 'pfam-n': false, funfam: false, }; + + flattenedData = flattenTracksObject(dataMerged).filter( + (track) => !renamedTracks.includes(track[0]), + ); } return ( - - {children} - + <> + + {children} + + ); }; diff --git a/src/components/Related/DomainsOnProtein/index.tsx b/src/components/Related/DomainsOnProtein/index.tsx index 29d47f57f..7444d8bab 100644 --- a/src/components/Related/DomainsOnProtein/index.tsx +++ b/src/components/Related/DomainsOnProtein/index.tsx @@ -25,7 +25,10 @@ import mergeResidues from './mergeResidues'; import DomainsOnProteinLoaded, { makeTracks } from './DomainsOnProteinLoaded'; import loadExternalSources, { ExtenalSourcesProps } from './ExternalSourcesHOC'; import { ProteinsAPIVariation } from '@nightingale-elements/nightingale-variation/dist/proteinAPI'; -import { ExtendedFeature } from 'src/components/ProteinViewer'; +import { + ExtendedFeature, + ExtendedFeatureLocation, +} from 'src/components/ProteinViewer'; export const orderByAccession = ( a: { accession: string }, @@ -74,6 +77,195 @@ interface LoadedProps PayloadList> | ErrorPayload > {} +const getFeature = ( + filter: string | string[], + mergedData: ProteinViewerDataObject, +): ExtendedFeature[] => { + if (mergedData['other_features']) { + return (mergedData['other_features'] as ExtendedFeature[]).filter( + (entry) => { + const entryDB = entry.source_database?.toLowerCase(); + if (entryDB) { + if (Array.isArray(filter)) + return filter.some((item) => entryDB.includes(item)); + else return filter.includes(entryDB); + } + }, + ); + } + return []; +}; + +const filterMobiDBLiteFeatures = ( + mergedData: ProteinViewerDataObject, +): ExtendedFeature[] => { + const mobiDBLiteEntries: ExtendedFeature[] = ( + mergedData['other_features'] as ExtendedFeature[] + ).filter((k) => + (k as ExtendedFeature).accession.toLowerCase().includes('mobidb'), + ); + + const mobiDBLiteConsensusWithChildren: ExtendedFeature[] = + mobiDBLiteEntries.filter( + (entry) => + entry.accession.toLowerCase().includes('consensus') || + entry.name?.toLowerCase().includes('consensus'), + ); + + const mobiDBLiteChildren: ExtendedFeature[] = mobiDBLiteEntries.filter( + (entry) => + !entry.accession.toLowerCase().includes('consensus') && + !entry.name?.toLowerCase().includes('consensus'), + ); + + if (mobiDBLiteConsensusWithChildren.length > 0) { + mobiDBLiteChildren.map((child) => { + child.protein = child.accession; + }); + mobiDBLiteConsensusWithChildren[0].children = mobiDBLiteChildren; + } + + return mobiDBLiteConsensusWithChildren; +}; + +export const sectionsReorganization = (mergedData: ProteinViewerDataObject) => { + // Domain and family as empty objects, to cancat other object later + if (!mergedData.domain) { + mergedData.domain = []; + } + + if (!mergedData.family) { + mergedData.family = []; + } + + // Add repeats and homologous superfamilies to domain + if (mergedData.homologous_superfamily) { + mergedData.domain = mergedData.domain.concat( + mergedData.homologous_superfamily, + ); + mergedData.homologous_superfamily = []; + } + + if (mergedData.repeat) { + mergedData.domain = mergedData.domain.concat(mergedData.repeat); + mergedData.repeat = []; + } +}; + +export const proteinViewerReorganization = ( + dataFeatures: RequestedData | undefined, + dataMerged: ProteinViewerDataObject, +) => { + if ( + (dataFeatures && !dataFeatures.loading && dataFeatures.payload) || + dataMerged['other_features'] + ) { + dataMerged['intrinsically_disordered_regions'] = filterMobiDBLiteFeatures( + dataMerged, + ) as MinimalFeature[]; + } + + // Splitting the "other features" section in mulitple subsets. + // Using this logic we can go back to having the "other_features" section again. + + // Create a section for each of the following types + const CPST = ['coils', 'phobius', 'signalp', 'tmhmm']; + dataMerged['coiled-coils,_signal_peptides,_transmembrane_regions'] = + getFeature(CPST, dataMerged) as MinimalFeature[]; + dataMerged['pfam-n'] = getFeature('pfam-n', dataMerged) as MinimalFeature[]; + dataMerged['short_linear_motifs'] = getFeature( + 'elm', + dataMerged, + ) as MinimalFeature[]; + dataMerged['funfam'] = getFeature('funfam', dataMerged) as MinimalFeature[]; + + if (Object.keys(dataMerged).includes('region')) { + dataMerged['spurious_proteins'] = dataMerged['region']; + delete dataMerged['region']; + } + + // Filter the types above out of the "other_features" section + const toRemove = CPST.concat([ + 'pfam-n', + 'short_linear_motifs', + 'mobidblt', + 'funfam', + 'elm', + ]); + + if (dataMerged['other_features']) { + dataMerged['other_features'] = dataMerged['other_features'].filter( + (entry) => { + return !toRemove.some( + (item) => (entry as ExtendedFeature).source_database?.includes(item), + ); + }, + ); + } + + const uniqueResidues: Record = {}; + + // Group PIRSR residue by description and position + let pirsrFound = false; + for (let i = 0; i < dataMerged.residues?.length; i++) { + const currentResidue = dataMerged.residues[i] as ExtendedFeature; + if (currentResidue.source_database === 'pirsr') { + currentResidue.accession = currentResidue.accession.replace( + 'residue:', + '', + ); + if (!pirsrFound) pirsrFound = true; + const residueStart = + currentResidue.locations?.[0].fragments?.[0].start || 0; + const residueEnd = currentResidue.locations?.[0].fragments?.[0].end || 0; + const residueDescription = + currentResidue.locations?.[0].description?.replace('.', ''); + + const dictKey = + residueStart.toString() + residueEnd.toString() + residueDescription; + + if (!uniqueResidues[dictKey]) uniqueResidues[dictKey] = currentResidue; + } else { + uniqueResidues[currentResidue.accession] = currentResidue; + } + } + + // Create fake PIRSR object to display group label + if (pirsrFound) + uniqueResidues['PIRSR'] = { + accession: 'PIRSR_GROUP', + source_database: 'pirsr', + type: 'residue', + locations: [ + { + description: 'PIRSR', + fragments: [{ residues: '', start: -10, end: 0 }], + } as ExtendedFeatureLocation, + ], + }; + + dataMerged.conserved_residues = Object.values(uniqueResidues).sort((a, b) => { + // If comparing two entries from different DBs, put the non-pirsr always first (a) OR if source database is pirsr and first element is fake label, put fake label first + if ( + (a.source_database !== 'pirsr' && b.source_database === 'pirsr') || + (a.source_database === b.source_database && a.accession === 'PIRSR_GROUP') + ) + return -1; + // If comparing two entries from different DBs, put the non-pirsr always first (b) OR if source database is pirsr and second element is fake label, put fake label first + else if ( + (a.source_database === 'pirsr' && b.source_database !== 'pirsr') || + (a.source_database === b.source_database && b.accession === 'PIRSR_GROUP') + ) + return 1; + // All other cases + else return a.accession.localeCompare(b.accession); + }); + + if (dataMerged.domain) dataMerged.domains = dataMerged.domain.slice(); + + if (dataMerged.family) dataMerged.families = dataMerged.family.slice(); +}; + const DomainOnProteinWithoutData = ({ data, mainData, @@ -172,95 +364,12 @@ const DomainOnProteinWithoutData = ({ mergeResidues(mergedData, dataResidues.payload); } - const getFeature = ( - filter: string | string[], - mergedData: ProteinViewerDataObject, - ): ExtendedFeature[] => { - if (mergedData['other_features']) { - return (mergedData['other_features'] as ExtendedFeature[]).filter( - (entry) => { - const entryDB = entry.source_database; - if (entryDB) { - if (Array.isArray(filter)) - return filter.some((item) => entryDB.includes(item)); - else return filter.includes(entryDB); - } - }, - ); - } - return []; - }; - - const filterMobiDBLiteFeatures = ( - mergedData: ProteinViewerDataObject, - ): ExtendedFeature[] => { - const mobiDBLiteEntries: ExtendedFeature[] = ( - mergedData['other_features'] as ExtendedFeature[] - ).filter((k) => (k as ExtendedFeature).accession.includes('Mobidblt')); - - const mobiDBLiteConsensusWithChildren: ExtendedFeature[] = - mobiDBLiteEntries.filter((entry) => - entry.accession.includes('Consensus'), - ); - const mobiDBLiteChildren: ExtendedFeature[] = mobiDBLiteEntries.filter( - (entry) => !entry.accession.includes('Consensus'), - ); - - if (mobiDBLiteConsensusWithChildren.length > 0) { - mobiDBLiteChildren.map((child) => { - child.protein = child.accession; - }); - mobiDBLiteConsensusWithChildren[0].children = mobiDBLiteChildren; - } - - return mobiDBLiteConsensusWithChildren; - }; - if (dataFeatures && !dataFeatures.loading && dataFeatures.payload) { mergeExtraFeatures(mergedData, dataFeatures?.payload); - mergedData['intrinsically_disordered_regions'] = filterMobiDBLiteFeatures( - mergedData, - ) as MinimalFeature[]; - - /* Splitting the "other features" section in mulitple subsets. - Using this logic we can go back to having the "other_features" section again. - */ - - // Create a section for each of the following types - const CPST = ['coils', 'phobius', 'signalp', 'tmhmm']; - mergedData['coiled-coils,_signal_peptides,_transmembrane_regions'] = - getFeature(CPST, mergedData) as MinimalFeature[]; - mergedData['pfam-n'] = getFeature('pfam-n', mergedData) as MinimalFeature[]; - mergedData['short_linear_motifs'] = getFeature( - 'elm', - mergedData, - ) as MinimalFeature[]; - mergedData['funfam'] = getFeature('funfam', mergedData) as MinimalFeature[]; - - if (Object.keys(mergedData).includes('region')) { - mergedData['spurious_proteins'] = mergedData['region']; - delete mergedData['region']; - } - - // - - // Filter the types above out of the "other_features" section - const toRemove = CPST.concat([ - 'pfam-n', - 'short_linear_motifs', - 'mobidblt', - 'funfam', - 'elm', - ]); - mergedData['other_features'] = mergedData['other_features'].filter( - (entry) => { - return !toRemove.some((item) => entry.source_database?.includes(item)); - }, - ); - - /* End of logic for splitting "other_features" */ } + proteinViewerReorganization(dataFeatures, mergedData); + if ( (!Object.keys(mergedData).length || !Object.values(mergedData) @@ -282,6 +391,7 @@ const DomainOnProteinWithoutData = ({ dataConfidence={dataConfidence} dataVariation={dataVariation} dataProteomics={dataProteomics} + dataFeatures={dataFeatures} loading={ data?.loading || dataFeatures?.loading || From 959cb091f631b43e3e6dd5f39f10c827604ae8b8 Mon Sep 17 00:00:00 2001 From: apolignano Date: Fri, 24 Jan 2025 11:14:05 +0000 Subject: [PATCH 2/8] Update snapshot --- src/components/EBIFooter/__snapshots__/test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EBIFooter/__snapshots__/test.js.snap b/src/components/EBIFooter/__snapshots__/test.js.snap index f957faada..0ac2fd946 100644 --- a/src/components/EBIFooter/__snapshots__/test.js.snap +++ b/src/components/EBIFooter/__snapshots__/test.js.snap @@ -366,7 +366,7 @@ exports[` should render 1`] = ` className="vf-footer__legal-text" > Copyright © EMBL - 2024 + 2025 Date: Fri, 24 Jan 2025 12:55:59 +0000 Subject: [PATCH 3/8] export getFeature and filterMobiDBLiteFeatures --- src/components/Related/DomainsOnProtein/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Related/DomainsOnProtein/index.tsx b/src/components/Related/DomainsOnProtein/index.tsx index 7444d8bab..d8c36b709 100644 --- a/src/components/Related/DomainsOnProtein/index.tsx +++ b/src/components/Related/DomainsOnProtein/index.tsx @@ -77,7 +77,7 @@ interface LoadedProps PayloadList> | ErrorPayload > {} -const getFeature = ( +export const getFeature = ( filter: string | string[], mergedData: ProteinViewerDataObject, ): ExtendedFeature[] => { @@ -96,7 +96,7 @@ const getFeature = ( return []; }; -const filterMobiDBLiteFeatures = ( +export const filterMobiDBLiteFeatures = ( mergedData: ProteinViewerDataObject, ): ExtendedFeature[] => { const mobiDBLiteEntries: ExtendedFeature[] = ( From 4d655ac7d1b0cc43579a77faa59fcecb7aa16acc Mon Sep 17 00:00:00 2001 From: apolignano Date: Fri, 24 Jan 2025 12:58:40 +0000 Subject: [PATCH 4/8] Redeclaration error --- .../Related/DomainsOnProtein/index.tsx | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/src/components/Related/DomainsOnProtein/index.tsx b/src/components/Related/DomainsOnProtein/index.tsx index 508ffab0b..4d33ad92b 100644 --- a/src/components/Related/DomainsOnProtein/index.tsx +++ b/src/components/Related/DomainsOnProtein/index.tsx @@ -56,52 +56,6 @@ export const groupByEntryType = ( return groups; }; -export const getFeature = ( - filter: string | string[], - mergedData: ProteinViewerDataObject, -): ExtendedFeature[] => { - if (mergedData['other_features']) { - return (mergedData['other_features'] as ExtendedFeature[]).filter( - (entry) => { - const entryDB = entry.source_database; - if (entryDB) { - if (Array.isArray(filter)) - return filter.some((item) => entryDB.includes(item)); - else return filter.includes(entryDB); - } - }, - ); - } - return []; -}; - -export const filterMobiDBLiteFeatures = ( - mergedData: ProteinViewerDataObject, -): ExtendedFeature[] => { - const mobiDBLiteEntries: ExtendedFeature[] = ( - mergedData['other_features'] as ExtendedFeature[] - ).filter((k) => - (k as ExtendedFeature).accession.toLowerCase().includes('mobidblt'), - ); - - const mobiDBLiteConsensusWithChildren: ExtendedFeature[] = - mobiDBLiteEntries.filter((entry) => - entry.accession.toLowerCase().includes('consensus'), - ); - const mobiDBLiteChildren: ExtendedFeature[] = mobiDBLiteEntries.filter( - (entry) => !entry.accession.toLowerCase().includes('consensus'), - ); - - if (mobiDBLiteConsensusWithChildren.length > 0) { - mobiDBLiteChildren.map((child) => { - child.protein = child.accession; - }); - mobiDBLiteConsensusWithChildren[0].children = mobiDBLiteChildren; - } - - return mobiDBLiteConsensusWithChildren; -}; - type Props = PropsWithChildren<{ mainData: { metadata: From caa40fc13b4b4b6adf3b4be14f5566794ac0ac58 Mon Sep 17 00:00:00 2001 From: apolignano Date: Mon, 27 Jan 2025 10:27:19 +0000 Subject: [PATCH 5/8] Add view mode --- src/components/ProteinViewer/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/ProteinViewer/index.tsx b/src/components/ProteinViewer/index.tsx index 7d9393a51..114085843 100644 --- a/src/components/ProteinViewer/index.tsx +++ b/src/components/ProteinViewer/index.tsx @@ -365,7 +365,6 @@ export const ProteinViewer = ({ {children} {protein.accession && - !protein.accession.startsWith('iprscan') && mainTracks.length !== Object.entries(hideCategories).length && ( Date: Tue, 28 Jan 2025 13:25:46 +0000 Subject: [PATCH 6/8] Unintegrated section, sfld bug --- src/components/IPScan/Summary/serializers.ts | 7 ++-- .../DomainsOnProteinLoaded/index.tsx | 35 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/components/IPScan/Summary/serializers.ts b/src/components/IPScan/Summary/serializers.ts index 44f358b6c..89ffc73ba 100644 --- a/src/components/IPScan/Summary/serializers.ts +++ b/src/components/IPScan/Summary/serializers.ts @@ -58,7 +58,7 @@ const integrateSignature = ( const match2residues = (match: Iprscan5Match | IpScanMatch) => { return match.locations .map(({ sites }) => - sites + sites?.length && sites?.length > 0 ? { accession: match?.signature?.accession || match?.accession, locations: sites.map((site) => ({ @@ -179,7 +179,10 @@ export const mergeData = ( signature: undefined, }; + console.log(match); const residues = match2residues(match); + console.log(residues); + if ( residues.length > 0 && !OTHER_RESIDUES_DBS.includes(residues?.[0]?.source_database || '') @@ -216,7 +219,7 @@ export const mergeData = ( if (residues[0] && residues[0].locations.length !== 0) mergedData.other_residues.push(residues[0]); } else { - // unintegrated[mergedMatch.accession] = mergedMatch; + unintegrated[mergedMatch.accession] = mergedMatch; } const representativeLocations = processedMatch.locations.filter( diff --git a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx index fc3fa5249..560b77791 100644 --- a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx +++ b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren } from 'react'; +import React, { PropsWithChildren, useEffect } from 'react'; import { addConfidenceTrack } from 'components/Structure/ViewerAndEntries/ProteinViewerForAlphafold'; import loadable from 'higherOrder/loadable'; import { @@ -314,6 +314,38 @@ const DomainsOnProteinLoaded = ({ const renamedTracks = ['domain', 'family', 'residues']; let flattenedData = undefined; + useEffect(() => { + // Move matches from unintegrated section to the correct one + if (protein.accession.startsWith('iprscan')) { + const dbToSection: Record = { + cathgene3d: 'homologous_superfamily', + cdd: 'domain', + hamap: 'family', + panther: 'family', + pirsf: 'family', + pirsr: 'residue', + sfld: 'family', + smart: 'domain', + sff: 'homologous_superfamily', + }; + + if (dataMerged.unintegrated) { + for (let i = 0; i < dataMerged.unintegrated.length; i++) { + const sourcedb = (dataMerged.unintegrated[i] as ExtendedFeature) + .source_database; + if (sourcedb && Object.keys(dbToSection).includes(sourcedb)) { + if (dataMerged[dbToSection[sourcedb]]) { + dataMerged[dbToSection[sourcedb]] = dataMerged[ + dbToSection[sourcedb] + ].concat([dataMerged.unintegrated[i]]); + } + delete dataMerged.unintegrated[i]; + } + } + } + } + }, [dataMerged]); + if (dataConfidence) addConfidenceTrack(dataConfidence, protein.accession, dataMerged); @@ -424,7 +456,6 @@ const DomainsOnProteinLoaded = ({ funfam: false, }; } else { - mainTracks = [ 'alphafold confidence', 'families', From 336e550623415de594403be8597ac2043eeb102f Mon Sep 17 00:00:00 2001 From: apolignano Date: Thu, 30 Jan 2025 15:59:32 +0000 Subject: [PATCH 7/8] Requested changes --- src/components/IPScan/Summary/serializers.ts | 2 - src/components/Matches/index.tsx | 37 +++++----- .../ProteinViewer/Popup/Entry/index.tsx | 5 +- .../DomainsOnProteinLoaded/index.tsx | 73 ++++++++++--------- src/subPages/Sequence/index.tsx | 4 +- 5 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/components/IPScan/Summary/serializers.ts b/src/components/IPScan/Summary/serializers.ts index 89ffc73ba..f169b140a 100644 --- a/src/components/IPScan/Summary/serializers.ts +++ b/src/components/IPScan/Summary/serializers.ts @@ -179,9 +179,7 @@ export const mergeData = ( signature: undefined, }; - console.log(match); const residues = match2residues(match); - console.log(residues); if ( residues.length > 0 && diff --git a/src/components/Matches/index.tsx b/src/components/Matches/index.tsx index e0e54b23f..c79b5c735 100644 --- a/src/components/Matches/index.tsx +++ b/src/components/Matches/index.tsx @@ -401,30 +401,33 @@ const Matches = ({ ( - { + console.log(name); + return ( + - - - )} + > + + + ); + }} > Short Name diff --git a/src/components/ProteinViewer/Popup/Entry/index.tsx b/src/components/ProteinViewer/Popup/Entry/index.tsx index 6e1bf1209..798826f1e 100644 --- a/src/components/ProteinViewer/Popup/Entry/index.tsx +++ b/src/components/ProteinViewer/Popup/Entry/index.tsx @@ -18,7 +18,7 @@ export type EntryDetail = { type: string; entry: string; protein?: string; - parent?: { protein?: string }; + parent?: { protein?: string; accession: string }; locations: Array; confidence?: string; }; @@ -55,7 +55,8 @@ const ProtVistaEntryPopup = ({ confidence, } = detail?.feature || {}; const isInterPro = sourceDatabase.toLowerCase() === 'interpro'; - const integrated = detail.feature?.integrated; + const integrated = + detail.feature?.integrated || detail.feature?.parent?.accession; // To include the type of fragment of the secondary structure let type = originalType; diff --git a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx index 560b77791..e0a105126 100644 --- a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx +++ b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx @@ -1,4 +1,9 @@ -import React, { PropsWithChildren, useEffect } from 'react'; +import React, { + PropsWithChildren, + useEffect, + useState, + useLayoutEffect, +} from 'react'; import { addConfidenceTrack } from 'components/Structure/ViewerAndEntries/ProteinViewerForAlphafold'; import loadable from 'higherOrder/loadable'; import { @@ -314,38 +319,6 @@ const DomainsOnProteinLoaded = ({ const renamedTracks = ['domain', 'family', 'residues']; let flattenedData = undefined; - useEffect(() => { - // Move matches from unintegrated section to the correct one - if (protein.accession.startsWith('iprscan')) { - const dbToSection: Record = { - cathgene3d: 'homologous_superfamily', - cdd: 'domain', - hamap: 'family', - panther: 'family', - pirsf: 'family', - pirsr: 'residue', - sfld: 'family', - smart: 'domain', - sff: 'homologous_superfamily', - }; - - if (dataMerged.unintegrated) { - for (let i = 0; i < dataMerged.unintegrated.length; i++) { - const sourcedb = (dataMerged.unintegrated[i] as ExtendedFeature) - .source_database; - if (sourcedb && Object.keys(dbToSection).includes(sourcedb)) { - if (dataMerged[dbToSection[sourcedb]]) { - dataMerged[dbToSection[sourcedb]] = dataMerged[ - dbToSection[sourcedb] - ].concat([dataMerged.unintegrated[i]]); - } - delete dataMerged.unintegrated[i]; - } - } - } - } - }, [dataMerged]); - if (dataConfidence) addConfidenceTrack(dataConfidence, protein.accession, dataMerged); @@ -414,6 +387,18 @@ const DomainsOnProteinLoaded = ({ }); } + const dbToSection: Record = { + cathgene3d: 'homologous_superfamily', + cdd: 'domain', + hamap: 'family', + panther: 'family', + pirsf: 'family', + pirsr: 'residue', + sfld: 'family', + smart: 'domain', + sff: 'homologous_superfamily', + }; + flattenedData.map((entry) => { if (entry[0] === 'domains') { if (representative_domains) { @@ -424,7 +409,29 @@ const DomainsOnProteinLoaded = ({ } else if (entry[0] === 'representative domains') { entry[1] = []; } + + // Move entries from unintegrated section to the correct one + else if (entry[0] === 'unintegrated') { + const tempUnintegrated = entry[1]; + for (let i = 0; i < tempUnintegrated.length; i++) { + if (tempUnintegrated[i]) { + const sourcedb = (tempUnintegrated[i] as ExtendedFeature) + .source_database; + if (sourcedb && Object.keys(dbToSection).includes(sourcedb)) { + if (dataMerged[dbToSection[sourcedb]]) { + dataMerged[dbToSection[sourcedb]] = dataMerged[ + dbToSection[sourcedb] + ].concat([tempUnintegrated[i]]); + } + tempUnintegrated.splice(i, 1); + } + } else { + console.log('here', tempUnintegrated, tempUnintegrated[i]); + } + } + } }); + // End of skipped reorganization steps mainTracks = [ diff --git a/src/subPages/Sequence/index.tsx b/src/subPages/Sequence/index.tsx index e73d31694..edec7a5c6 100644 --- a/src/subPages/Sequence/index.tsx +++ b/src/subPages/Sequence/index.tsx @@ -52,7 +52,7 @@ const SequenceSubPage = ({ data, localPayload, localTitle, orf }: Props) => { return ( <> - {hasORF && protein && ( + {hasORF && protein ? (
Nucleotide Sequence
{ name={protein.xref[0].name} />
+ ) : ( + '' )} ); From 75357e8389ffa5cc8f263adda9e0c20758790233 Mon Sep 17 00:00:00 2001 From: apolignano Date: Fri, 31 Jan 2025 11:43:01 +0000 Subject: [PATCH 8/8] Short name in IPScan results entries table --- src/components/IPScan/EntrySubPage/index.tsx | 1 + src/components/Matches/index.tsx | 43 ++++++++++--------- .../DomainsOnProteinLoaded/index.tsx | 2 - 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/IPScan/EntrySubPage/index.tsx b/src/components/IPScan/EntrySubPage/index.tsx index 8b32238d2..b5658f019 100644 --- a/src/components/IPScan/EntrySubPage/index.tsx +++ b/src/components/IPScan/EntrySubPage/index.tsx @@ -18,6 +18,7 @@ const flatMatchesFromIPScanPayload = function* ( yield { accession: match.signature.entry.accession, name: match.signature.entry.description, + short_name: match.signature.entry.name, source_database: 'InterPro', matches: [ { diff --git a/src/components/Matches/index.tsx b/src/components/Matches/index.tsx index c79b5c735..7d3dd13f9 100644 --- a/src/components/Matches/index.tsx +++ b/src/components/Matches/index.tsx @@ -401,33 +401,34 @@ const Matches = ({ { - console.log(name); - return ( - ( + - - - ); - }} + } + > + + + )} > Short Name diff --git a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx index e0a105126..6ca0009cb 100644 --- a/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx +++ b/src/components/Related/DomainsOnProtein/DomainsOnProteinLoaded/index.tsx @@ -425,8 +425,6 @@ const DomainsOnProteinLoaded = ({ } tempUnintegrated.splice(i, 1); } - } else { - console.log('here', tempUnintegrated, tempUnintegrated[i]); } } }