diff --git a/src/public/app/entities/note_short.js b/src/public/app/entities/note_short.js index 271bc0b3c..68e14f3be 100644 --- a/src/public/app/entities/note_short.js +++ b/src/public/app/entities/note_short.js @@ -1,5 +1,6 @@ import server from '../services/server.js'; import Attribute from './attribute.js'; +import noteAttributeCache from "../services/note_attribute_cache.js"; const LABEL = 'label'; const LABEL_DEFINITION = 'label-definition'; @@ -156,9 +157,9 @@ class NoteShort { getOwnedAttributes(type, name) { const attrs = this.attributes .map(attributeId => this.treeCache.attributes[attributeId]) - .filter(attr => !!attr); + .filter(Boolean); // filter out nulls; - return this.__filterAttrs(attrs, type, name) + return this.__filterAttrs(attrs, type, name); } /** @@ -167,43 +168,45 @@ class NoteShort { * @returns {Attribute[]} all note's attributes, including inherited ones */ getAttributes(type, name) { - const ownedAttributes = this.getOwnedAttributes(); + if (!(this.noteId in noteAttributeCache)) { + const ownedAttributes = this.getOwnedAttributes(); - const attrArrs = [ - ownedAttributes - ]; + const attrArrs = [ + ownedAttributes + ]; - for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) { - const templateNote = this.treeCache.getNoteFromCache(templateAttr.value); + for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) { + const templateNote = this.treeCache.notes[templateAttr.value]; - if (templateNote) { - attrArrs.push(templateNote.getAttributes()); - } - } - - if (this.noteId !== 'root') { - for (const parentNote of this.getParentNotes()) { - // these virtual parent-child relationships are also loaded into frontend tree cache - if (parentNote.type !== 'search') { - attrArrs.push(parentNote.getInheritableAttributes()); + if (templateNote) { + attrArrs.push(templateNote.getAttributes()); } } + + if (this.noteId !== 'root') { + for (const parentNote of this.getParentNotes()) { + // these virtual parent-child relationships are also loaded into frontend tree cache + if (parentNote.type !== 'search') { + attrArrs.push(parentNote.getInheritableAttributes()); + } + } + } + + noteAttributeCache.attributes[this.noteId] = attrArrs.flat(); } - const attributes = attrArrs.flat(); - - return this.__filterAttrs(attributes, type, name); + return this.__filterAttrs(noteAttributeCache.attributes[this.noteId], type, name); } __filterAttrs(attributes, type, name) { - if (type && name) { + if (!type && !name) { + return attributes; + } else if (type && name) { return attributes.filter(attr => attr.type === type && attr.name === name); } else if (type) { return attributes.filter(attr => attr.type === type); } else if (name) { return attributes.filter(attr => attr.name === name); - } else { - return attributes; } } diff --git a/src/public/app/services/load_results.js b/src/public/app/services/load_results.js index c51707686..ddd174a24 100644 --- a/src/public/app/services/load_results.js +++ b/src/public/app/services/load_results.js @@ -101,6 +101,15 @@ export default class LoadResults { this.options.includes(name); } + /** + * @return {boolean} true if there are changes which could affect the attributes (including inherited ones) + */ + hasAttributeRelatedChanges() { + return Object.keys(this.noteIdToSourceId).length === 0 + && this.branches.length === 0 + && this.attributes.length === 0; + } + isEmpty() { return Object.keys(this.noteIdToSourceId).length === 0 && this.branches.length === 0 diff --git a/src/public/app/services/note_attribute_cache.js b/src/public/app/services/note_attribute_cache.js new file mode 100644 index 000000000..bf53cb528 --- /dev/null +++ b/src/public/app/services/note_attribute_cache.js @@ -0,0 +1,20 @@ +/** + * Purpose of this class is to cache list of attributes for notes. + * + * Cache invalidation granularity is global - whenever a write operation is detected to notes, branches or attributes + * we invalidate the whole cache. That's OK, since the purpose for this is to speed up batch read-only operations, such + * as loading the tree which uses attributes heavily. + */ +class NoteAttributeCache { + constructor() { + this.attributes = {}; + } + + invalidate() { + this.attributes = {}; + } +} + +const noteAttributeCache = new NoteAttributeCache(); + +export default noteAttributeCache; \ No newline at end of file diff --git a/src/public/app/services/ws.js b/src/public/app/services/ws.js index c590d7e46..d06cac0fb 100644 --- a/src/public/app/services/ws.js +++ b/src/public/app/services/ws.js @@ -6,6 +6,7 @@ import Branch from "../entities/branch.js"; import Attribute from "../entities/attribute.js"; import options from "./options.js"; import treeCache from "./tree_cache.js"; +import noteAttributeCache from "./note_attribute_cache.js"; const $outstandingSyncsCount = $("#outstanding-syncs-count"); @@ -359,6 +360,10 @@ async function processSyncRows(syncRows) { }); if (!loadResults.isEmpty()) { + if (loadResults.hasAttributeRelatedChanges()) { + noteAttributeCache.invalidate(); + } + const appContext = (await import("./app_context.js")).default; await appContext.triggerEvent('entitiesReloaded', {loadResults}); }