From a1d4e062ed2e06800d93f5f2a2de90e16af41cc0 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 16 Apr 2023 09:22:24 +0200 Subject: [PATCH] refactoring of "some path" WIP --- src/becca/becca_service.js | 128 +----------------- src/becca/entities/bnote.js | 15 ++ src/becca/similarity.js | 2 +- src/routes/api/recent_changes.js | 22 +-- .../search/expressions/note_flat_text.js | 13 +- src/services/search/services/search.js | 2 +- 6 files changed, 45 insertions(+), 137 deletions(-) diff --git a/src/becca/becca_service.js b/src/becca/becca_service.js index 47e7d39e2..d4a4269bb 100644 --- a/src/becca/becca_service.js +++ b/src/becca/becca_service.js @@ -24,49 +24,12 @@ function isNotePathArchived(notePath) { return false; } -/** - * This assumes that note is available. "archived" note means that there isn't a single non-archived note-path - * leading to this note. - * - * @param noteId - */ -function isArchived(noteId) { - const notePath = getSomePath(noteId); - - return isNotePathArchived(notePath); -} - -/** - * @param {string} noteId - * @param {string} ancestorNoteId - * @returns {boolean} - true if given noteId has ancestorNoteId in any of its paths (even archived) - */ -function isInAncestor(noteId, ancestorNoteId) { - if (ancestorNoteId === 'root' || ancestorNoteId === noteId) { - return true; - } - - const note = becca.notes[noteId]; - - if (!note) { - return false; - } - - for (const parentNote of note.parents) { - if (isInAncestor(parentNote.noteId, ancestorNoteId)) { - return true; - } - } - - return false; -} - function getNoteTitle(childNoteId, parentNoteId) { const childNote = becca.notes[childNoteId]; const parentNote = becca.notes[parentNoteId]; if (!childNote) { - log.info(`Cannot find note in cache for noteId '${childNoteId}'`); + log.info(`Cannot find note '${childNoteId}'`); return "[error fetching title]"; } @@ -119,86 +82,15 @@ function getNoteTitleForPath(notePathArray) { return titles.join(' / '); } -/** - * Returns notePath for noteId. Note hoisting is respected. - * Archived (and hidden) notes are also returned, but non-archived paths are preferred if available - * - this means that archived paths is returned only if there's no non-archived path - * - you can check whether returned path is archived using isArchived - * - * @param {BNote} note - * @param {string[]} path - */ -function getSomePath(note, path = []) { - // first try to find note within hoisted note, otherwise take any existing note path - return getSomePathInner(note, path, true) - || getSomePathInner(note, path, false); -} - -/** - * @param {BNote} note - * @param {string[]} parentPath - * @param {boolean} respectHoisting - * @returns {string[]|false} - */ -function getSomePathInner(note, parentPath, respectHoisting) { - const childPath = [...parentPath, note.noteId]; - if (note.isRoot()) { - childPath.reverse(); - - if (respectHoisting && !childPath.includes(cls.getHoistedNoteId())) { - return false; - } - - return childPath; - } - - const parents = note.parents; - if (parents.length === 0) { - console.log(`Note '${note.noteId}' - '${note.title}' has no parents.`); - - return false; - } - - const completeNotePaths = parents.map(parentNote => getSomePathInner(parentNote, childPath, respectHoisting)); - - if (completeNotePaths.length === 0) { - return false; - } else if (completeNotePaths.length === 1) { - return completeNotePaths[0]; - } else { - completeNotePaths.sort((a, b) => { - if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { - return a.isInHoistedSubTree ? -1 : 1; - } else if (a.isSearch !== b.isSearch) { - return a.isSearch ? 1 : -1; - } else if (a.isArchived !== b.isArchived) { - return a.isArchived ? 1 : -1; - } else if (a.isHidden !== b.isHidden) { - return a.isHidden ? 1 : -1; - } else { - return a.notePath.length - b.notePath.length; - } - }); - - // if there are multiple valid paths, take the shortest one - const shortestNotePath = completeNotePaths.reduce((shortestPath, nextPath) => - nextPath.length < shortestPath.length - ? nextPath - : shortestPath, completeNotePaths[0]); - - return shortestNotePath; - } -} - function getNotePath(noteId) { const note = becca.notes[noteId]; if (!note) { - console.trace(`Cannot find note '${noteId}' in cache.`); + console.trace(`Cannot find note '${noteId}'.`); return; } - const retPath = getSomePath(note); + const retPath = note.getBestNotePath(); if (retPath) { const noteTitle = getNoteTitleForPath(retPath); @@ -223,23 +115,9 @@ function getNotePath(noteId) { } } -/** - * @param noteId - * @returns {boolean} - true if note exists (is not deleted) and is available in current note hoisting - */ -function isAvailable(noteId) { - const notePath = getNotePath(noteId); - - return !!notePath; -} - module.exports = { - getSomePath, getNotePath, getNoteTitle, getNoteTitleForPath, - isAvailable, - isArchived, - isInAncestor, isNotePathArchived }; diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 625ba1f4b..0f1b3ea8b 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -748,6 +748,21 @@ class BNote extends AbstractBeccaEntity { return this.hasAttribute('label', 'archived'); } + areAllNotePathsArchived() { + // there's a slight difference between note being itself archived and all its note paths being archived + // - note is archived when it itself has an archived label or inherits it + // - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable) + // archived label + + const bestNotePathRecord = this.getSortedNotePathRecords()[0]; + + if (!bestNotePathRecord) { + throw new Error(`No note path available for note '${this.noteId}'`); + } + + return bestNotePathRecord.isArchived; + } + hasInheritableArchivedLabel() { for (const attr of this.getAttributes()) { if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { diff --git a/src/becca/similarity.js b/src/becca/similarity.js index 2e7750100..8900bb87d 100644 --- a/src/becca/similarity.js +++ b/src/becca/similarity.js @@ -404,7 +404,7 @@ async function findSimilarNotes(noteId) { let score = computeScore(candidateNote); if (score >= 1.5) { - const notePath = beccaService.getSomePath(candidateNote); + const notePath = candidateNote.getBestNotePath(); // this takes care of note hoisting if (!notePath) { diff --git a/src/routes/api/recent_changes.js b/src/routes/api/recent_changes.js index 2d54af93b..646898e9c 100644 --- a/src/routes/api/recent_changes.js +++ b/src/routes/api/recent_changes.js @@ -3,14 +3,14 @@ const sql = require('../../services/sql'); const protectedSessionService = require('../../services/protected_session'); const noteService = require('../../services/notes'); -const beccaService = require('../../becca/becca_service'); +const becca = require("../../becca/becca"); function getRecentChanges(req) { const {ancestorNoteId} = req.params; let recentChanges = []; - const noteRevisions = sql.getRows(` + const noteRevisionRows = sql.getRows(` SELECT notes.noteId, notes.isDeleted AS current_isDeleted, @@ -24,16 +24,18 @@ function getRecentChanges(req) { note_revisions JOIN notes USING(noteId)`); - for (const noteRevision of noteRevisions) { - if (beccaService.isInAncestor(noteRevision.noteId, ancestorNoteId)) { - recentChanges.push(noteRevision); + for (const noteRevisionRow of noteRevisionRows) { + const note = becca.getNote(noteRevisionRow.noteId); + + if (note?.hasAncestor(ancestorNoteId)) { + recentChanges.push(noteRevisionRow); } } // now we need to also collect date points not represented in note revisions: // 1. creation for all notes (dateCreated) // 2. deletion for deleted notes (dateModified) - const notes = sql.getRows(` + const noteRows = sql.getRows(` SELECT notes.noteId, notes.isDeleted AS current_isDeleted, @@ -57,9 +59,11 @@ function getRecentChanges(req) { FROM notes WHERE notes.isDeleted = 1`); - for (const note of notes) { - if (beccaService.isInAncestor(note.noteId, ancestorNoteId)) { - recentChanges.push(note); + for (const noteRow of noteRows) { + const note = becca.getNote(noteRow.noteId); + + if (note?.hasAncestor(ancestorNoteId)) { + recentChanges.push(noteRow); } } diff --git a/src/services/search/expressions/note_flat_text.js b/src/services/search/expressions/note_flat_text.js index 863573a13..024160985 100644 --- a/src/services/search/expressions/note_flat_text.js +++ b/src/services/search/expressions/note_flat_text.js @@ -24,7 +24,7 @@ class NoteFlatTextExp extends Expression { */ function searchDownThePath(note, tokens, path) { if (tokens.length === 0) { - const retPath = beccaService.getSomePath(note, path); + const retPath = this.getNotePath(note, path); if (retPath) { const noteId = retPath[retPath.length - 1]; @@ -131,6 +131,17 @@ class NoteFlatTextExp extends Expression { return resultNoteSet; } + getNotePath(note, path) { + if (path.length === 0) { + return note.getBestNotePath(); + } else { + const closestNoteId = path[0]; + const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePathString(); + + return [...closestNoteBestNotePath, ...path.slice(1)]; + } + } + /** * Returns noteIds which have at least one matching tokens * diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js index f1adc72d7..3e36040ab 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.js @@ -157,7 +157,7 @@ function findResultsWithExpression(expression, searchContext) { const searchResults = noteSet.notes .filter(note => !note.isDeleted) .map(note => { - const notePathArray = executionContext.noteIdToNotePath[note.noteId] || beccaService.getSomePath(note); + const notePathArray = executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath(); if (!notePathArray) { throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`);