full search shows search errors, closes #3221

This commit is contained in:
zadam 2022-12-17 13:07:42 +01:00
parent c496519095
commit c28383de4f
15 changed files with 76 additions and 30 deletions

View File

@ -176,7 +176,7 @@ class Froca {
return;
}
const {searchResultNoteIds, highlightedTokens} = await server.get('search-note/' + note.noteId);
const {searchResultNoteIds, highlightedTokens, error} = await server.get('search-note/' + note.noteId);
if (!Array.isArray(searchResultNoteIds)) {
throw new Error(`Search note '${note.noteId}' failed: ${searchResultNoteIds}`);
@ -208,6 +208,8 @@ class Froca {
froca.notes[note.noteId].searchResultsLoaded = true;
froca.notes[note.noteId].highlightedTokens = highlightedTokens;
return {error};
}
/** @returns {NoteShort[]} */

View File

@ -250,7 +250,11 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
async refreshResultsCommand() {
try {
await froca.loadSearchNote(this.noteId);
const {error} = await froca.loadSearchNote(this.noteId);
if (error) {
this.handleEvent('showSearchError', { error });
}
}
catch (e) {
toastService.showError(e.message);

View File

@ -71,6 +71,18 @@ export default class SearchString extends AbstractSearchOption {
return $option;
}
showSearchErrorEvent({error}) {
this.$searchString.tooltip({
trigger: 'manual',
title: "Search error: " + error,
placement: 'bottom'
});
this.$searchString.tooltip("show");
setTimeout(() => this.$searchString.tooltip("dispose"), 4000);
}
focusOnSearchDefinitionEvent() {
this.$searchString.focus();
}

View File

@ -385,9 +385,10 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
font-size: var(--main-font-size) !important;
}
.tooltip .arrow::before {
border-right-color: var(--main-border-color) !important;
}
.bs-tooltip-bottom .arrow::before { border-bottom-color: var(--main-border-color) !important; }
.bs-tooltip-top .arrow::before { border-top-color: var(--main-border-color) !important; }
.bs-tooltip-left .arrow::before { border-left-color: var(--main-border-color) !important; }
.bs-tooltip-right .arrow::before { border-right-color: var(--main-border-color) !important; }
.note-tooltip.tooltip .arrow {
display: none;

View File

@ -21,9 +21,9 @@ class AndExp extends Expression {
this.subExpressions = subExpressions;
}
execute(inputNoteSet, executionContext) {
execute(inputNoteSet, executionContext, searchContext) {
for (const subExpression of this.subExpressions) {
inputNoteSet = subExpression.execute(inputNoteSet, executionContext);
inputNoteSet = subExpression.execute(inputNoteSet, executionContext, searchContext);
}
return inputNoteSet;

View File

@ -10,14 +10,14 @@ class ChildOfExp extends Expression {
this.subExpression = subExpression;
}
execute(inputNoteSet, executionContext) {
execute(inputNoteSet, executionContext, searchContext) {
const subInputNoteSet = new NoteSet();
for (const note of inputNoteSet.notes) {
subInputNoteSet.addAll(note.parents);
}
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext);
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext, searchContext);
const resNoteSet = new NoteSet();

View File

@ -11,9 +11,9 @@ class DescendantOfExp extends Expression {
this.subExpression = subExpression;
}
execute(inputNoteSet, executionContext) {
execute(inputNoteSet, executionContext, searchContext) {
const subInputNoteSet = new NoteSet(Object.values(becca.notes));
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext);
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext, searchContext);
const subTreeNoteSet = new NoteSet();

View File

@ -8,9 +8,10 @@ class Expression {
/**
* @param {NoteSet} inputNoteSet
* @param {object} executionContext
* @param {SearchContext} searchContext
* @return {NoteSet}
*/
execute(inputNoteSet, executionContext) {}
execute(inputNoteSet, executionContext, searchContext) {}
}
module.exports = Expression;

View File

@ -9,8 +9,8 @@ class NotExp extends Expression {
this.subExpression = subExpression;
}
execute(inputNoteSet, executionContext) {
const subNoteSet = this.subExpression.execute(inputNoteSet, executionContext);
execute(inputNoteSet, executionContext, searchContext) {
const subNoteSet = this.subExpression.execute(inputNoteSet, executionContext, searchContext);
return inputNoteSet.minus(subNoteSet);
}

View File

@ -8,7 +8,7 @@ const protectedSessionService = require('../../protected_session');
const striptags = require('striptags');
const utils = require("../../utils");
const ALLOWED_OPERATORS = ['*=*', '=', '*=', '=*', '%='];
const ALLOWED_OPERATORS = ['=', '!=', '*=*', '*=', '=*', '%='];
const cachedRegexes = {};
@ -24,17 +24,19 @@ class NoteContentFulltextExp extends Expression {
constructor(operator, {tokens, raw, flatText}) {
super();
if (!ALLOWED_OPERATORS.includes(operator)) {
throw new Error(`Note content can be searched only with operators: ` + ALLOWED_OPERATORS.join(", ") + `, operator ${operator} given.`);
}
this.operator = operator;
this.tokens = tokens;
this.raw = !!raw;
this.flatText = !!flatText;
}
execute(inputNoteSet) {
execute(inputNoteSet, executionContext, searchContext) {
if (!ALLOWED_OPERATORS.includes(this.operator)) {
searchContext.addError(`Note content can be searched only with operators: ` + ALLOWED_OPERATORS.join(", ") + `, operator ${this.operator} given.`);
return inputNoteSet;
}
const resultNoteSet = new NoteSet();
const sql = require('../../sql');
@ -66,6 +68,7 @@ class NoteContentFulltextExp extends Expression {
const [token] = this.tokens;
if ((this.operator === '=' && token === content)
|| (this.operator === '!=' && token !== content)
|| (this.operator === '*=' && content.endsWith(token))
|| (this.operator === '=*' && content.startsWith(token))
|| (this.operator === '*=*' && content.includes(token))

View File

@ -20,8 +20,8 @@ class OrderByAndLimitExp extends Expression {
this.subExpression = null; // it's expected to be set after construction
}
execute(inputNoteSet, executionContext) {
let {notes} = this.subExpression.execute(inputNoteSet, executionContext);
execute(inputNoteSet, executionContext, searchContext) {
let {notes} = this.subExpression.execute(inputNoteSet, executionContext, searchContext);
notes.sort((a, b) => {
for (const {valueExtractor, smaller, larger} of this.orderDefinitions) {

View File

@ -10,14 +10,14 @@ class ParentOfExp extends Expression {
this.subExpression = subExpression;
}
execute(inputNoteSet, executionContext) {
execute(inputNoteSet, executionContext, searchContext) {
const subInputNoteSet = new NoteSet();
for (const note of inputNoteSet.notes) {
subInputNoteSet.addAll(note.children);
}
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext);
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext, searchContext);
const resNoteSet = new NoteSet();

View File

@ -12,7 +12,7 @@ class RelationWhereExp extends Expression {
this.subExpression = subExpression;
}
execute(inputNoteSet, executionContext) {
execute(inputNoteSet, executionContext, searchContext) {
const candidateNoteSet = new NoteSet();
for (const attr of becca.findAttributes('relation', this.relationName)) {
@ -20,7 +20,7 @@ class RelationWhereExp extends Expression {
if (inputNoteSet.hasNoteId(note.noteId) && attr.targetNote) {
const subInputNoteSet = new NoteSet([attr.targetNote]);
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext);
const subResNoteSet = this.subExpression.execute(subInputNoteSet, executionContext, searchContext);
if (subResNoteSet.hasNote(attr.targetNote)) {
if (attr.isInheritable) {

View File

@ -18,6 +18,7 @@ const AncestorExp = require("../expressions/ancestor");
const buildComparator = require('./build_comparator');
const ValueExtractor = require('../value_extractor');
const utils = require("../../utils");
const TrueExp = require("../expressions/true.js");
function getFulltext(tokens, searchContext) {
tokens = tokens.map(t => utils.removeDiacritic(t.token));
@ -417,11 +418,22 @@ function getExpression(tokens, searchContext, level = 0) {
}
function parse({fulltextTokens, expressionTokens, searchContext}) {
let expression;
try {
expression = getExpression(expressionTokens, searchContext);
}
catch (e) {
searchContext.addError(e.message);
expression = new TrueExp();
}
let exp = AndExp.of([
searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", "=", "false"),
(searchContext.ancestorNoteId && searchContext.ancestorNoteId !== 'root') ? new AncestorExp(searchContext.ancestorNoteId, searchContext.ancestorDepth) : null,
getFulltext(fulltextTokens, searchContext),
getExpression(expressionTokens, searchContext)
expression
]);
if (searchContext.orderBy && searchContext.orderBy !== 'relevancy') {

View File

@ -17,6 +17,7 @@ function searchFromNote(note) {
const searchScript = note.getRelationValue('searchScript');
const searchString = note.getLabelValue('searchString');
let error = null;
if (searchScript) {
searchResultNoteIds = searchFromRelation(note, 'searchScript');
@ -38,13 +39,15 @@ function searchFromNote(note) {
.map(sr => sr.noteId);
highlightedTokens = searchContext.highlightedTokens;
error = searchContext.getError();
}
// we won't return search note's own noteId
// also don't allow root since that would force infinite cycle
return {
searchResultNoteIds: searchResultNoteIds.filter(resultNoteId => !['root', note.noteId].includes(resultNoteId)),
highlightedTokens
highlightedTokens,
error: error
};
}
@ -148,7 +151,7 @@ function findResultsWithExpression(expression, searchContext) {
noteIdToNotePath: {}
};
const noteSet = expression.execute(allNoteSet, executionContext);
const noteSet = expression.execute(allNoteSet, executionContext, searchContext);
const searchResults = noteSet.notes
.map(note => {
@ -197,7 +200,15 @@ function findResultsWithExpression(expression, searchContext) {
function parseQueryToExpression(query, searchContext) {
const {fulltextTokens, expressionTokens} = lex(query);
const structuredExpressionTokens = handleParens(expressionTokens);
let structuredExpressionTokens;
try {
structuredExpressionTokens = handleParens(expressionTokens);
}
catch (e) {
structuredExpressionTokens = [];
searchContext.addError(e.message);
}
const expression = parse({
fulltextTokens,