protvista-uniprot 4.6.0 → 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ type ProcessedStructureData = {
13
13
  declare class ProtvistaUniprotStructure extends LitElement {
14
14
  accession?: string;
15
15
  sequence?: string;
16
+ checksum?: string;
16
17
  data?: ProcessedStructureData[];
17
18
  structureId?: string;
18
19
  metaInfo?: TemplateResult;
@@ -28,6 +29,9 @@ declare class ProtvistaUniprotStructure extends LitElement {
28
29
  structureId: {
29
30
  type: StringConstructor;
30
31
  };
32
+ checksum: {
33
+ type: StringConstructor;
34
+ };
31
35
  sequence: {
32
36
  type: StringConstructor;
33
37
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "protvista-uniprot",
3
3
  "description": "ProtVista tool for the UniProt website",
4
- "version": "4.6.0",
4
+ "version": "4.6.2",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
@@ -33,6 +33,7 @@ const providersFrom3DBeacons = [
33
33
  ];
34
34
 
35
35
  const sourceMethods = new Map([
36
+ ['AlphaFold DB', 'Predicted'],
36
37
  ['SWISS-MODEL', 'Modeling'],
37
38
  ['ModelArchive', 'Modeling'],
38
39
  ['PED', 'Modeling'],
@@ -63,7 +64,7 @@ type Sequence = {
63
64
  };
64
65
 
65
66
  type BeaconsData = {
66
- uniprot_entry: {
67
+ uniprot_entry?: {
67
68
  ac: string;
68
69
  id: string;
69
70
  uniprot_checksum: string;
@@ -71,6 +72,11 @@ type BeaconsData = {
71
72
  segment_start: number;
72
73
  segment_end: number;
73
74
  };
75
+ entry?: {
76
+ sequence: string;
77
+ checksum: string;
78
+ checksum_type: string;
79
+ };
74
80
  structures: {
75
81
  summary: {
76
82
  model_identifier: string;
@@ -120,49 +126,52 @@ type ProcessedStructureData = {
120
126
  };
121
127
 
122
128
  const processPDBData = (data: UniProtKBData): ProcessedStructureData[] =>
123
- data.uniProtKBCrossReferences ? data.uniProtKBCrossReferences
124
- .filter((xref) => xref.database === 'PDB')
125
- .sort((refA, refB) => refA.id.localeCompare(refB.id))
126
- .map(({ id, properties }) => {
127
- if (!properties) {
128
- return;
129
- }
130
-
131
- const propertyMap = properties.reduce((acc, item) => {
132
- acc[item.key] = item.value;
133
- return acc;
134
- }, {} as Record<string, string>);
135
-
136
- const method = propertyMap['Method'];
137
- const resolution = propertyMap['Resolution'];
138
- const chains = propertyMap['Chains'];
139
-
140
- let chain;
141
- let positions;
142
- if (chains) {
143
- const tokens = chains.split('=');
144
- if (tokens.length === 2) {
145
- [chain, positions] = tokens;
146
- }
147
- }
148
- const output: ProcessedStructureData = {
149
- id,
150
- source: 'PDB',
151
- method,
152
- resolution: !resolution || resolution === '-' ? undefined : resolution,
153
- downloadLink: `https://www.ebi.ac.uk/pdbe/entry-files/download/pdb${id.toLowerCase()}.ent`,
154
- chain,
155
- positions,
156
- protvistaFeatureId: id,
157
- };
158
- return output;
159
- })
160
- .filter(
161
- (
162
- transformedItem: ProcessedStructureData | undefined
163
- ): transformedItem is ProcessedStructureData =>
164
- transformedItem !== undefined
165
- ) : [];
129
+ data.uniProtKBCrossReferences
130
+ ? data.uniProtKBCrossReferences
131
+ .filter((xref) => xref.database === 'PDB')
132
+ .sort((refA, refB) => refA.id.localeCompare(refB.id))
133
+ .map(({ id, properties }) => {
134
+ if (!properties) {
135
+ return;
136
+ }
137
+
138
+ const propertyMap = properties.reduce((acc, item) => {
139
+ acc[item.key] = item.value;
140
+ return acc;
141
+ }, {} as Record<string, string>);
142
+
143
+ const method = propertyMap['Method'];
144
+ const resolution = propertyMap['Resolution'];
145
+ const chains = propertyMap['Chains'];
146
+
147
+ let chain;
148
+ let positions;
149
+ if (chains) {
150
+ const tokens = chains.split('=');
151
+ if (tokens.length === 2) {
152
+ [chain, positions] = tokens;
153
+ }
154
+ }
155
+ const output: ProcessedStructureData = {
156
+ id,
157
+ source: 'PDB',
158
+ method,
159
+ resolution:
160
+ !resolution || resolution === '-' ? undefined : resolution,
161
+ downloadLink: `https://www.ebi.ac.uk/pdbe/entry-files/download/pdb${id.toLowerCase()}.ent`,
162
+ chain,
163
+ positions,
164
+ protvistaFeatureId: id,
165
+ };
166
+ return output;
167
+ })
168
+ .filter(
169
+ (
170
+ transformedItem: ProcessedStructureData | undefined
171
+ ): transformedItem is ProcessedStructureData =>
172
+ transformedItem !== undefined
173
+ )
174
+ : [];
166
175
 
167
176
  const processAFData = (data: AlphaFoldPayload): ProcessedStructureData[] =>
168
177
  data.map((d) => ({
@@ -174,23 +183,55 @@ const processAFData = (data: AlphaFoldPayload): ProcessedStructureData[] =>
174
183
  downloadLink: d.pdbUrl,
175
184
  }));
176
185
 
177
- const process3DBeaconsData = (data: BeaconsData): ProcessedStructureData[] => {
178
- const otherStructures = data?.structures?.filter(({ summary }) =>
179
- providersFrom3DBeacons.includes(summary.provider)
180
- );
186
+ const process3DBeaconsData = (
187
+ data: BeaconsData,
188
+ accession: string | undefined,
189
+ checksum: string | undefined
190
+ ): ProcessedStructureData[] => {
191
+ // If accession is provided without checksum, filter by whitelisted providers
192
+ const filterByProviders = !!accession && !checksum;
193
+
194
+ let structures = filterByProviders
195
+ ? data?.structures?.filter(({ summary }) =>
196
+ providersFrom3DBeacons.includes(summary.provider)
197
+ )
198
+ : data?.structures?.sort(
199
+ (a, b) =>
200
+ b.summary.confidence_avg_local_score -
201
+ a.summary.confidence_avg_local_score
202
+ );
203
+
204
+ if (accession && checksum && structures) {
205
+ const matchIndex = structures.findIndex(({ summary }) =>
206
+ summary.model_identifier.includes(accession)
207
+ );
208
+
209
+ if (matchIndex !== -1) {
210
+ structures = [
211
+ structures[matchIndex],
212
+ ...structures.slice(0, matchIndex),
213
+ ...structures.slice(matchIndex + 1),
214
+ ];
215
+ }
216
+ }
217
+
181
218
  return (
182
- otherStructures?.map(({ summary }) => ({
183
- id: summary['model_identifier'],
219
+ structures?.map(({ summary }) => ({
220
+ id: summary.model_identifier,
184
221
  source: summary.provider,
185
222
  method: sourceMethods.get(summary.provider),
186
- positions: `${summary['uniprot_start']}-${summary['uniprot_end']}`,
187
- protvistaFeatureId: summary['model_identifier'],
188
- downloadLink: summary['model_url'],
189
- // isoform.io does not have a model page url. Link to their homepage instead.
223
+ positions: `${summary.uniprot_start || 1}-${
224
+ summary.uniprot_end || data.entry?.sequence.length
225
+ }`,
226
+ protvistaFeatureId: summary.model_identifier,
227
+ downloadLink: summary.model_url,
190
228
  sourceDBLink:
191
229
  summary.provider === 'isoform.io'
192
230
  ? 'https://www.isoform.io/home'
193
- : summary['model_page_url'],
231
+ : summary.model_page_url,
232
+ chain:
233
+ summary.entities?.flatMap((entity) => entity.chain_ids).join(', ') ||
234
+ undefined,
194
235
  })) || []
195
236
  );
196
237
  };
@@ -261,6 +302,7 @@ const styleId = 'protvista-styles';
261
302
  class ProtvistaUniprotStructure extends LitElement {
262
303
  accession?: string;
263
304
  sequence?: string;
305
+ checksum?: string;
264
306
  data?: ProcessedStructureData[];
265
307
  structureId?: string;
266
308
  metaInfo?: TemplateResult;
@@ -286,6 +328,7 @@ class ProtvistaUniprotStructure extends LitElement {
286
328
  return {
287
329
  accession: { type: String },
288
330
  structureId: { type: String },
331
+ checksum: { type: String },
289
332
  sequence: { type: String },
290
333
  data: { type: Object },
291
334
  loading: { type: Boolean },
@@ -296,13 +339,23 @@ class ProtvistaUniprotStructure extends LitElement {
296
339
 
297
340
  async connectedCallback() {
298
341
  super.connectedCallback();
299
- if (!this.accession) return;
342
+ if (!this.accession && !this.checksum) return;
300
343
 
301
344
  // We are showing PDBe models returned by UniProt's API as there is inconsistency between UniProt's recognised ones and 3d-beacons.
302
- const pdbUrl = `https://rest.uniprot.org/uniprotkb/${this.accession}`;
303
- const alphaFoldUrl = `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}`;
304
- // exclude_provider accepts only value hence 'pdbe' as majority of the models are from there.
305
- const beaconsUrl = `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${this.accession}.json?exclude_provider=pdbe`;
345
+ const pdbUrl =
346
+ this.accession && !this.checksum
347
+ ? `https://rest.uniprot.org/uniprotkb/${this.accession}`
348
+ : '';
349
+ // AlphaMissense predictions are only available in AF predictions endpoint
350
+ const alphaFoldUrl =
351
+ this.accession && !this.checksum
352
+ ? `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}`
353
+ : '';
354
+ // exclude_provider accepts only value hence 'pdbe' as majority of the models are from there if querying by accession
355
+ const beaconsUrl =
356
+ this.accession && !this.checksum
357
+ ? `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${this.accession}.json?exclude_provider=pdbe`
358
+ : `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/v2/sequence/?id=${this.checksum}&type=md5`;
306
359
 
307
360
  const rawData = await fetchAll([pdbUrl, alphaFoldUrl, beaconsUrl]);
308
361
  this.loading = false;
@@ -311,7 +364,9 @@ class ProtvistaUniprotStructure extends LitElement {
311
364
  let afData = [];
312
365
  // Check if AF sequence matches UniProt sequence
313
366
  const alphaFoldSequenceMatch = rawData[alphaFoldUrl]?.filter(
314
- ({ sequence: afSequence }) => rawData[pdbUrl]?.sequence?.value === afSequence || this.sequence === afSequence
367
+ ({ sequence: afSequence }) =>
368
+ rawData[pdbUrl]?.sequence?.value === afSequence ||
369
+ this.sequence === afSequence
315
370
  );
316
371
  if (alphaFoldSequenceMatch?.length) {
317
372
  afData = processAFData(alphaFoldSequenceMatch);
@@ -320,7 +375,11 @@ class ProtvistaUniprotStructure extends LitElement {
320
375
  );
321
376
  }
322
377
 
323
- const beaconsData = process3DBeaconsData(rawData[beaconsUrl] || []);
378
+ const beaconsData = process3DBeaconsData(
379
+ rawData[beaconsUrl] || [],
380
+ this.accession,
381
+ this.checksum
382
+ );
324
383
 
325
384
  // TODO: return if no data at all
326
385
  // if (!payload) return;
@@ -341,7 +400,11 @@ class ProtvistaUniprotStructure extends LitElement {
341
400
  ) as ProtvistaDatatable;
342
401
  if (!protvistaDatatableElt?.selectedid && this.data?.[0]) {
343
402
  // Select the first element in the table
344
- this.onTableRowClick({ id: this.data[0].id, source: this.data[0].source, downloadLink: this.data[0].downloadLink });
403
+ this.onTableRowClick({
404
+ id: this.data[0].id,
405
+ source: this.data[0].source,
406
+ downloadLink: this.data[0].downloadLink,
407
+ });
345
408
  protvistaDatatableElt.selectedid = this.data[0].id;
346
409
  }
347
410
  }
@@ -377,12 +440,15 @@ class ProtvistaUniprotStructure extends LitElement {
377
440
  source?: string;
378
441
  downloadLink?: string;
379
442
  }) {
380
- if (providersFrom3DBeacons.includes(source)) {
443
+ if (this.checksum || providersFrom3DBeacons.includes(source)) {
381
444
  this.modelUrl = downloadLink;
382
445
  // Reset the rest
383
446
  this.structureId = undefined;
384
447
  this.metaInfo = undefined;
385
448
  this.colorTheme = 'alphafold';
449
+ if (source === 'AlphaFold DB') {
450
+ this.metaInfo = AFMetaInfo;
451
+ }
386
452
  } else {
387
453
  this.structureId = id;
388
454
  this.modelUrl = undefined;
@@ -613,7 +679,8 @@ class ProtvistaUniprotStructure extends LitElement {
613
679
  : html``}
614
680
  ${!this.data && !this.loading
615
681
  ? html`<div class="protvista-no-results">
616
- No structure information available for ${this.accession}
682
+ No structure information available
683
+ ${this.accession ? `for ${this.accession}` : ''}
617
684
  </div>`
618
685
  : html``}
619
686
  </div>
@@ -585,13 +585,14 @@ class ProtvistaUniprot extends LitElement {
585
585
  }
586
586
 
587
587
  handleCategoryClick(e: MouseEvent) {
588
- const target = e.target as Element;
588
+ let target = e.target as Element;
589
+
590
+ if (target instanceof HTMLSpanElement) {
591
+ target = target.parentElement as Element;
592
+ }
593
+
594
+ const toggle = target.getAttribute('data-category-toggle');
589
595
 
590
- const toggle =
591
- target.getAttribute('data-category-toggle') ||
592
- (target instanceof HTMLSpanElement &&
593
- target.parentElement?.getAttribute('data-category-toggle'));
594
-
595
596
  if (toggle && !target.classList.contains('open')) {
596
597
  target.classList.add('open');
597
598
  this.openCategories = [...this.openCategories, toggle];