From eac4a8988b8573806f80c1f92d93e8907d53f512 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Thu, 5 Sep 2019 16:09:05 +0200 Subject: [PATCH] mol-model: added Model.entryId; mol-plugin: toggle validation tooltip; fixed using correct entryId for various custom properties --- src/apps/basic-wrapper/index.html | 1 + src/apps/basic-wrapper/index.ts | 6 +++ .../proteopedia-wrapper/annotation.ts | 2 +- src/examples/proteopedia-wrapper/helpers.ts | 4 +- .../structure/mmcif/parser.ts | 2 + .../pdbe/structure-quality-report.ts | 2 +- src/mol-model-props/rcsb/assembly-symmetry.ts | 4 +- src/mol-model/structure/model/model.ts | 1 + .../structure/structure/properties.ts | 1 + .../pdbe/structure-quality-report.ts | 53 ++++++++++--------- src/mol-plugin/index.ts | 2 +- src/mol-plugin/state/transforms/model.ts | 20 ++++--- src/perf-tests/mol-script.ts | 2 +- .../model/properties/providers/pdbe.ts | 10 ++-- src/servers/model/server/query.ts | 2 +- 15 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html index 6c9f2d9d5..cb24e98ba 100644 --- a/src/apps/basic-wrapper/index.html +++ b/src/apps/basic-wrapper/index.html @@ -109,6 +109,7 @@ addControl('Static Superposition', () => BasicMolStarWrapper.tests.staticSuperposition()); addControl('Dynamic Superposition', () => BasicMolStarWrapper.tests.dynamicSuperposition()); + addControl('Validation Tooltip', () => BasicMolStarWrapper.tests.toggleValidationTooltip()); //////////////////////////////////////////////////////// diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index 4629abcf4..c722e95e4 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/src/apps/basic-wrapper/index.ts @@ -17,6 +17,7 @@ import { StateBuilder, StateTransform } from '../../mol-state'; import { StripedResidues } from './coloring'; // import { BasicWrapperControls } from './controls'; import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition'; +import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props'; require('mol-plugin/skin/light.scss') type SupportedFormats = 'cif' | 'pdb' @@ -152,6 +153,11 @@ class BasicWrapper { dynamicSuperposition: async () => { await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.plugin.state.dataState, ref: StateTransform.RootRef }); await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM'); + }, + toggleValidationTooltip: async () => { + const state = this.plugin.state.behaviorState; + const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip })); + await PluginCommands.State.Update.dispatch(this.plugin, { state, tree }); } } } diff --git a/src/examples/proteopedia-wrapper/annotation.ts b/src/examples/proteopedia-wrapper/annotation.ts index d628f628a..1d88eae3f 100644 --- a/src/examples/proteopedia-wrapper/annotation.ts +++ b/src/examples/proteopedia-wrapper/annotation.ts @@ -27,7 +27,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({ name: 'proteopedia-wrapper-evolutionary-conservation', display: 'Evolutionary Conservation', async getData(model: Model) { - const id = model.label.toLowerCase(); + const id = model.entryId.toLowerCase(); const req = await fetch(`https://proteopedia.org/cgi-bin/cnsrf?${id}`); const json = await req.json(); const annotations = (json && json.residueAnnotations) || []; diff --git a/src/examples/proteopedia-wrapper/helpers.ts b/src/examples/proteopedia-wrapper/helpers.ts index d8d4f2610..d376596d3 100644 --- a/src/examples/proteopedia-wrapper/helpers.ts +++ b/src/examples/proteopedia-wrapper/helpers.ts @@ -18,9 +18,9 @@ export interface ModelInfo { export namespace ModelInfo { async function getPreferredAssembly(ctx: PluginContext, model: Model) { - if (model.label.length <= 3) return void 0; + if (model.entryId.length <= 3) return void 0; try { - const id = model.label.toLowerCase(); + const id = model.entryId.toLowerCase(); const src = await ctx.runTask(ctx.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/${id}` })) as string; const json = JSON.parse(src); const data = json && json[id]; diff --git a/src/mol-model-formats/structure/mmcif/parser.ts b/src/mol-model-formats/structure/mmcif/parser.ts index 6d4647644..2ec01ddd4 100644 --- a/src/mol-model-formats/structure/mmcif/parser.ts +++ b/src/mol-model-formats/structure/mmcif/parser.ts @@ -217,6 +217,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIn return { id: UUID.create22(), + entryId: entry, label: label.join(' | '), entry, sourceData: format, @@ -253,6 +254,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD return { id: UUID.create22(), + entryId: entry, label: label.join(' | '), entry, sourceData: format, diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index ada52bbb5..7cc742b66 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -98,7 +98,7 @@ export namespace StructureQualityReport { { const url = mapUrl(model); const dataStr = await fetch({ url }).runInContext(ctx) as string; - const data = JSON.parse(dataStr)[model.label.toLowerCase()]; + const data = JSON.parse(dataStr)[model.entryId.toLowerCase()]; if (!data) return false; info = PropertyWrapper.createInfo(); issueMap = createIssueMapFromJson(model, data); diff --git a/src/mol-model-props/rcsb/assembly-symmetry.ts b/src/mol-model-props/rcsb/assembly-symmetry.ts index bdf36072c..d87065908 100644 --- a/src/mol-model-props/rcsb/assembly-symmetry.ts +++ b/src/mol-model-props/rcsb/assembly-symmetry.ts @@ -264,8 +264,8 @@ export namespace AssemblySymmetry { db = createDatabaseFromCif(model) } else { let result: AssemblySymmetryGraphQL.Query - console.log('model.label.toLowerCase()', model.label.toLowerCase()) - const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.label.toLowerCase() }; + console.log('model.entryId.toLowerCase()', model.entryId.toLowerCase()) + const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.entryId.toLowerCase() }; try { console.log('foo', client) result = await client.request<AssemblySymmetryGraphQL.Query>(ctx || RuntimeContext.Synchronous, query, variables); diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 22d1ae04e..de0649f49 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -23,6 +23,7 @@ import { ModelFormat } from '../../../mol-model-formats/structure/format'; */ export interface Model extends Readonly<{ id: UUID, + entryId: string, label: string, /** the name of the entry/file/collection the model is part of */ diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts index 046c32f78..5ca9b5caa 100644 --- a/src/mol-model/structure/structure/properties.ts +++ b/src/mol-model/structure/structure/properties.ts @@ -139,6 +139,7 @@ const unit = { operator_name: p(l => l.unit.conformation.operator.name), model_index: p(l => l.unit.model.modelNum), model_label: p(l => l.unit.model.label), + model_entry_id: p(l => l.unit.model.entryId), hkl: p(l => l.unit.conformation.operator.hkl), spgrOp: p(l => l.unit.conformation.operator.spgrOp), diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts index 91bf463a6..0b937d954 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts @@ -14,13 +14,13 @@ import { PluginBehavior } from '../../../behavior'; import { ThemeDataContext } from '../../../../../mol-theme/theme'; import { CustomPropertyRegistry } from '../../../../../mol-model-props/common/custom-property-registry'; -export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({ +export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({ name: 'pdbe-structure-quality-report-prop', category: 'custom-props', display: { name: 'PDBe Structure Quality Report' }, - ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> { + ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> { private attach = StructureQualityReport.createAttachTask( - m => `https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${m.label.toLowerCase()}`, + m => `https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${m.entryId.toLowerCase()}`, this.ctx.fetch ); @@ -32,9 +32,27 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo attach: this.attach } + private labelPDBeValidation = (loci: Loci): string | undefined => { + if (!this.params.showTooltip) return void 0; + + switch (loci.kind) { + case 'element-loci': + const e = loci.elements[0]; + const u = e.unit; + if (!u.model.customProperties.has(StructureQualityReport.Descriptor)) return void 0; + + const se = StructureElement.Location.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]); + const issues = StructureQualityReport.getIssues(se); + if (issues.length === 0) return 'PDBe Validation: No Issues'; + return `PDBe Validation: ${issues.join(', ')}`; + + default: return void 0; + } + } + register(): void { this.ctx.customModelProperties.register(this.provider); - this.ctx.lociLabels.addProvider(labelPDBeValidation); + this.ctx.lociLabels.addProvider(this.labelPDBeValidation); this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', { label: 'PDBe Structure Quality Report', @@ -45,37 +63,22 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo }) } - update(p: { autoAttach: boolean }) { + update(p: { autoAttach: boolean, showTooltip: boolean }) { let updated = this.params.autoAttach !== p.autoAttach this.params.autoAttach = p.autoAttach; + this.params.showTooltip = p.showTooltip; this.provider.defaultSelected = p.autoAttach; return updated; } unregister() { - console.log('unregister') this.ctx.customModelProperties.unregister(StructureQualityReport.Descriptor.name); - this.ctx.lociLabels.removeProvider(labelPDBeValidation); + this.ctx.lociLabels.removeProvider(this.labelPDBeValidation); this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('pdbe-structure-quality-report') } }, params: () => ({ - autoAttach: PD.Boolean(false) + autoAttach: PD.Boolean(false), + showTooltip: PD.Boolean(true) }) -}); - -function labelPDBeValidation(loci: Loci): string | undefined { - switch (loci.kind) { - case 'element-loci': - const e = loci.elements[0]; - const u = e.unit; - if (!u.model.customProperties.has(StructureQualityReport.Descriptor)) return void 0; - - const se = StructureElement.Location.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]); - const issues = StructureQualityReport.getIssues(se); - if (issues.length === 0) return 'PDBe Validation: No Issues'; - return `PDBe Validation: ${issues.join(', ')}`; - - default: return void 0; - } -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 209daad2c..5dffcda53 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -66,7 +66,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 8, extraRadius: 4 }), // PluginSpec.Behavior(PluginBehaviors.Labels.SceneLabels), PluginSpec.Behavior(PluginBehaviors.CustomProps.MolstarSecondaryStructure, { autoAttach: true }), - PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true }), + PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true, showTooltip: true }), PluginSpec.Behavior(PluginBehaviors.CustomProps.RCSBAssemblySymmetry, { autoAttach: true }), PluginSpec.Behavior(StructureRepresentationInteraction) ], diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 6df5fc2fe..2b4136293 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -228,12 +228,12 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ if (model.symmetry.assemblies.length === 0) { if (id !== 'deposited') { - plugin.log.warn(`Model '${a.data.label}' has no assembly, returning deposited structure.`); + plugin.log.warn(`Model '${a.data.entryId}' has no assembly, returning deposited structure.`); } } else { asm = ModelSymmetry.findAssembly(model, id || ''); if (!asm) { - plugin.log.warn(`Model '${a.data.label}' has no assembly called '${id}', returning deposited structure.`); + plugin.log.warn(`Model '${a.data.entryId}' has no assembly called '${id}', returning deposited structure.`); } } @@ -533,8 +533,12 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({ }); async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, names: string[]) { for (const name of names) { - const p = ctx.customModelProperties.get(name); - await p.attach(model).runInContext(taskCtx); + try { + const p = ctx.customModelProperties.get(name); + await p.attach(model).runInContext(taskCtx); + } catch (e) { + ctx.log.warn(`Error attaching model prop '${name}': ${e}`); + } } } @@ -558,8 +562,12 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({ }); async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, names: string[]) { for (const name of names) { - const p = ctx.customStructureProperties.get(name); - await p.attach(structure).runInContext(taskCtx); + try { + const p = ctx.customStructureProperties.get(name); + await p.attach(structure).runInContext(taskCtx); + } catch (e) { + ctx.log.warn(`Error attaching structure prop '${name}': ${e}`); + } } } diff --git a/src/perf-tests/mol-script.ts b/src/perf-tests/mol-script.ts index 0ff78a678..3c9d21c48 100644 --- a/src/perf-tests/mol-script.ts +++ b/src/perf-tests/mol-script.ts @@ -69,7 +69,7 @@ export async function testQ() { await StructureQualityReport.attachFromCifOrApi(structure.models[0], { PDBe_apiSourceJson: async model => { - const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, { timeout: 1500 }); + const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.entryId.toLowerCase()}`, { timeout: 1500 }); return await rawData.json(); } }) diff --git a/src/servers/model/properties/providers/pdbe.ts b/src/servers/model/properties/providers/pdbe.ts index 7935d4a49..3cdf25817 100644 --- a/src/servers/model/properties/providers/pdbe.ts +++ b/src/servers/model/properties/providers/pdbe.ts @@ -40,14 +40,14 @@ namespace residuewise_outlier_summary { // This is for "testing" purposes and should probably only read // a single file with the appropriate prop in the "production" version. return async (model: Model) => { - const key = `${model.label[1]}${model.label[2]}`; + const key = `${model.entryId[1]}${model.entryId[2]}`; if (!json.has(key)) { const fn = path.join(pathPrefix, `${key}.json`); if (!fs.existsSync(fn)) json.set(key, { }); // TODO: use async readFile? else json.set(key, JSON.parse(fs.readFileSync(fn, 'utf8'))); } - return json.get(key)![model.label.toLowerCase()] || { }; + return json.get(key)![model.entryId.toLowerCase()] || { }; } } } @@ -74,15 +74,15 @@ function apiQueryProvider(urlPrefix: string, cache: any) { return async (model: Model) => { try { if (cache[cacheKey]) return cache[cacheKey]; - const rawData = await fetchRetry(`${urlPrefix}/${model.label.toLowerCase()}`, 1500, 5); + const rawData = await fetchRetry(`${urlPrefix}/${model.entryId.toLowerCase()}`, 1500, 5); // TODO: is this ok? if (rawData.status !== 200) return { }; - const json = (await rawData.json())[model.label.toLowerCase()] || { }; + const json = (await rawData.json())[model.entryId.toLowerCase()] || { }; cache[cacheKey] = json; return json; } catch (e) { // TODO: handle better - ConsoleLogger.warn('Props', `Count not retrieve prop @${`${urlPrefix}/${model.label.toLowerCase()}`}`); + ConsoleLogger.warn('Props', `Count not retrieve prop @${`${urlPrefix}/${model.entryId.toLowerCase()}`}`); return { }; } } diff --git a/src/servers/model/server/query.ts b/src/servers/model/server/query.ts index 9bbcf807c..726e74a96 100644 --- a/src/servers/model/server/query.ts +++ b/src/servers/model/server/query.ts @@ -72,7 +72,7 @@ export async function resolveJob(job: Job): Promise<CifWriter.Encoder<any>> { ConsoleLogger.logId(job.id, 'Query', 'Query finished.'); perf.start('encode'); - encoder.startDataBlock(sourceStructures[0].models[0].label.toUpperCase()); + encoder.startDataBlock(sourceStructures[0].models[0].entryId.toUpperCase()); encoder.writeCategory(_model_server_result, job); encoder.writeCategory(_model_server_params, job); -- GitLab