From fc6d65aabcb3c5b20232cfe9dc5c879ee4e823a5 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 21 Aug 2024 15:07:46 +0200 Subject: [PATCH 01/14] simplify AST for multiselect values --- src/libs/SearchParser/searchParser.js | 831 ++++++++++++----------- src/libs/SearchParser/searchParser.peggy | 139 ++-- 2 files changed, 512 insertions(+), 458 deletions(-) diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 64d5334aa265..b4fb138f5db3 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -3,6 +3,11 @@ // https://peggyjs.org/ + +function buildFilter(operator, left, right) { + return { operator, left, right }; +} + function peg$subclass(child, parent) { function C() { this.constructor = child; } C.prototype = parent.prototype; @@ -176,29 +181,29 @@ function peg$parse(input, options) { var peg$startRuleFunction = peg$parsequery; var peg$c0 = "!="; - var peg$c1 = ">"; - var peg$c2 = ">="; - var peg$c3 = "<"; - var peg$c4 = "<="; - var peg$c5 = "type"; - var peg$c6 = "status"; - var peg$c7 = "date"; - var peg$c8 = "amount"; - var peg$c9 = "expenseType"; - var peg$c10 = "in"; - var peg$c11 = "currency"; - var peg$c12 = "merchant"; - var peg$c13 = "description"; - var peg$c14 = "from"; - var peg$c15 = "to"; - var peg$c16 = "category"; - var peg$c17 = "tag"; - var peg$c18 = "taxRate"; - var peg$c19 = "cardID"; - var peg$c20 = "reportID"; - var peg$c21 = "keyword"; - var peg$c22 = "sortBy"; - var peg$c23 = "sortOrder"; + var peg$c1 = ">="; + var peg$c2 = ">"; + var peg$c3 = "<="; + var peg$c4 = "<"; + var peg$c5 = "date"; + var peg$c6 = "amount"; + var peg$c7 = "expenseType"; + var peg$c8 = "merchant"; + var peg$c9 = "description"; + var peg$c10 = "reportID"; + var peg$c11 = "keyword"; + var peg$c12 = "type"; + var peg$c13 = "status"; + var peg$c14 = "sortBy"; + var peg$c15 = "sortOrder"; + var peg$c16 = "in"; + var peg$c17 = "currency"; + var peg$c18 = "tag"; + var peg$c19 = "category"; + var peg$c20 = "to"; + var peg$c21 = "taxRate"; + var peg$c22 = "cardID"; + var peg$c23 = "from"; var peg$c24 = "\""; var peg$r0 = /^[:=]/; @@ -208,99 +213,86 @@ function peg$parse(input, options) { var peg$e0 = peg$classExpectation([":", "="], false, false); var peg$e1 = peg$literalExpectation("!=", false); - var peg$e2 = peg$literalExpectation(">", false); - var peg$e3 = peg$literalExpectation(">=", false); - var peg$e4 = peg$literalExpectation("<", false); - var peg$e5 = peg$literalExpectation("<=", false); - var peg$e6 = peg$literalExpectation("type", false); - var peg$e7 = peg$literalExpectation("status", false); - var peg$e8 = peg$literalExpectation("date", false); - var peg$e9 = peg$literalExpectation("amount", false); - var peg$e10 = peg$literalExpectation("expenseType", false); - var peg$e11 = peg$literalExpectation("in", false); - var peg$e12 = peg$literalExpectation("currency", false); - var peg$e13 = peg$literalExpectation("merchant", false); - var peg$e14 = peg$literalExpectation("description", false); - var peg$e15 = peg$literalExpectation("from", false); - var peg$e16 = peg$literalExpectation("to", false); - var peg$e17 = peg$literalExpectation("category", false); - var peg$e18 = peg$literalExpectation("tag", false); - var peg$e19 = peg$literalExpectation("taxRate", false); - var peg$e20 = peg$literalExpectation("cardID", false); - var peg$e21 = peg$literalExpectation("reportID", false); - var peg$e22 = peg$literalExpectation("keyword", false); - var peg$e23 = peg$literalExpectation("sortBy", false); - var peg$e24 = peg$literalExpectation("sortOrder", false); - var peg$e25 = peg$literalExpectation("\"", false); - var peg$e26 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e27 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); - var peg$e28 = peg$otherExpectation("whitespace"); - var peg$e29 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e2 = peg$literalExpectation(">=", false); + var peg$e3 = peg$literalExpectation(">", false); + var peg$e4 = peg$literalExpectation("<=", false); + var peg$e5 = peg$literalExpectation("<", false); + var peg$e6 = peg$otherExpectation("key"); + var peg$e7 = peg$literalExpectation("date", false); + var peg$e8 = peg$literalExpectation("amount", false); + var peg$e9 = peg$literalExpectation("expenseType", false); + var peg$e10 = peg$literalExpectation("merchant", false); + var peg$e11 = peg$literalExpectation("description", false); + var peg$e12 = peg$literalExpectation("reportID", false); + var peg$e13 = peg$literalExpectation("keyword", false); + var peg$e14 = peg$otherExpectation("default key"); + var peg$e15 = peg$literalExpectation("type", false); + var peg$e16 = peg$literalExpectation("status", false); + var peg$e17 = peg$literalExpectation("sortBy", false); + var peg$e18 = peg$literalExpectation("sortOrder", false); + var peg$e19 = peg$otherExpectation("multiselect key"); + var peg$e20 = peg$literalExpectation("in", false); + var peg$e21 = peg$literalExpectation("currency", false); + var peg$e22 = peg$literalExpectation("tag", false); + var peg$e23 = peg$literalExpectation("category", false); + var peg$e24 = peg$literalExpectation("to", false); + var peg$e25 = peg$literalExpectation("taxRate", false); + var peg$e26 = peg$literalExpectation("cardID", false); + var peg$e27 = peg$literalExpectation("from", false); + var peg$e28 = peg$otherExpectation("quote"); + var peg$e29 = peg$literalExpectation("\"", false); + var peg$e30 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e31 = peg$otherExpectation("word"); + var peg$e32 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); + var peg$e33 = peg$otherExpectation("and"); + var peg$e34 = peg$otherExpectation("whitespace"); + var peg$e35 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { - const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null); - if (!allFilters.length) { - return null; - } - const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") - const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") - if(!nonKeywords.length){ - return keywords.reduce((result, filter) => buildFilter("or", result, filter)) - } - if(!keywords.length){ - return nonKeywords.reduce((result, filter) => buildFilter("and", result, filter)) - } - - return buildFilter("and", keywords.reduce((result, filter) => buildFilter("or", result, filter)), nonKeywords.reduce((result, filter) => buildFilter("and", result, filter))) - - - return allFilters.reduce((result, filter) => buildFilter("and", result, filter)); - }; - var peg$f2 = function(field, op, value) { - if (isDefaultField(field)) { - updateDefaultValues(field, value.trim()); - return null; - } + const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null).filter(Boolean); + if (!allFilters.length) { + return null; + } - if (!field && !op) { - return buildFilter('eq', 'keyword', value.trim()); - } + const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") + const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") - const values = value.split(','); - const operatorValue = op ?? 'eq'; + if(!nonKeywords.length){ + return keywords.reduce((result, filter) => buildFilter("or", result, filter)) + } + if(!keywords.length){ + return nonKeywords.reduce((result, filter) => buildFilter("and", result, filter)) + } - return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(operatorValue, field, val.trim())), buildFilter(operatorValue, field, values[0])); - }; - var peg$f3 = function() { return "eq"; }; - var peg$f4 = function() { return "neq"; }; - var peg$f5 = function() { return "gt"; }; - var peg$f6 = function() { return "gte"; }; - var peg$f7 = function() { return "lt"; }; - var peg$f8 = function() { return "lte"; }; - var peg$f9 = function() { return "type"; }; - var peg$f10 = function() { return "status"; }; - var peg$f11 = function() { return "date"; }; - var peg$f12 = function() { return "amount"; }; - var peg$f13 = function() { return "expenseType"; }; - var peg$f14 = function() { return "in"; }; - var peg$f15 = function() { return "currency"; }; - var peg$f16 = function() { return "merchant"; }; - var peg$f17 = function() { return "description"; }; - var peg$f18 = function() { return "from"; }; - var peg$f19 = function() { return "to"; }; - var peg$f20 = function() { return "category"; }; - var peg$f21 = function() { return "tag"; }; - var peg$f22 = function() { return "taxRate"; }; - var peg$f23 = function() { return "cardID"; }; - var peg$f24 = function() { return "reportID"; }; - var peg$f25 = function() { return "keyword"; }; - var peg$f26 = function() { return "sortBy"; }; - var peg$f27 = function() { return "sortOrder"; }; - var peg$f28 = function(parts) { return parts.join(''); }; - var peg$f29 = function(chars) { return chars.join(''); }; - var peg$f30 = function(chars) { return chars.join(''); }; - var peg$f31 = function() { return "and"; }; + return buildFilter("and", keywords.reduce((result, filter) => buildFilter("or", result, filter)), nonKeywords.reduce((result, filter) => buildFilter("and", result, filter))) + + return allFilters.reduce((result, filter) => buildFilter("and", result, filter)); +}; + var peg$f2 = function(key, op, value) { + updateDefaultValues(key, value.trim()); +}; + var peg$f3 = function(value) { + return buildFilter('eq', 'keyword', value.trim()); +}; + var peg$f4 = function(field, op, value) { + return buildFilter(op, field, value) +}; + var peg$f5 = function(field, op, value) { + + const values = value.split(','); + return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(op, field, val.trim())), buildFilter(op, field, values[0])); +}; + var peg$f6 = function() { return "eq"; }; + var peg$f7 = function() { return "neq"; }; + var peg$f8 = function() { return "gte"; }; + var peg$f9 = function() { return "gt"; }; + var peg$f10 = function() { return "lte"; }; + var peg$f11 = function() { return "lt"; }; + var peg$f12 = function(parts) { return parts.join(''); }; + var peg$f13 = function(chars) { return chars.join(''); }; + var peg$f14 = function(chars) { return chars.join(''); }; + var peg$f15 = function() { return "and"; }; var peg$currPos = options.peg$currPos | 0; var peg$savedPos = peg$currPos; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -520,24 +512,132 @@ function peg$parse(input, options) { } function peg$parsefilter() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parsestandardFilter(); + if (s1 === peg$FAILED) { + s1 = peg$parsedefaultFilter(); + if (s1 === peg$FAILED) { + s1 = peg$parsemultiSelectFilter(); + if (s1 === peg$FAILED) { + s1 = peg$parsefreeTextFilter(); + } + } + } + if (s1 !== peg$FAILED) { + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsedefaultFilter() { var s0, s1, s2, s3, s4, s5, s6; s0 = peg$currPos; s1 = peg$parse_(); - s2 = peg$parsekey(); - if (s2 === peg$FAILED) { - s2 = null; - } - s3 = peg$parse_(); - s4 = peg$parseoperator(); - if (s4 === peg$FAILED) { - s4 = null; + s2 = peg$parsedefaultKey(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + s4 = peg$parseoperator(); + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + s6 = peg$parseidentifier(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s2, s4, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; } - s5 = peg$parse_(); - s6 = peg$parseidentifier(); - if (s6 !== peg$FAILED) { + + return s0; + } + + function peg$parsefreeTextFilter() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseidentifier(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); peg$savedPos = s0; - s0 = peg$f2(s2, s4, s6); + s0 = peg$f3(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsemultiSelectFilter() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parsemultiselectKey(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + s4 = peg$parseoperator(); + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + s6 = peg$parseidentifier(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s2, s4, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsestandardFilter() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parsekey(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + s4 = peg$parseoperator(); + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + s6 = peg$parseidentifier(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s2, s4, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } else { peg$currPos = s0; s0 = peg$FAILED; @@ -559,7 +659,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f3(); + s1 = peg$f6(); } s0 = s1; if (s0 === peg$FAILED) { @@ -573,63 +673,63 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f4(); + s1 = peg$f7(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 62) { + if (input.substr(peg$currPos, 2) === peg$c1) { s1 = peg$c1; - peg$currPos++; + peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e2); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f5(); + s1 = peg$f8(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c2) { + if (input.charCodeAt(peg$currPos) === 62) { s1 = peg$c2; - peg$currPos += 2; + peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e3); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f6(); + s1 = peg$f9(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 60) { + if (input.substr(peg$currPos, 2) === peg$c3) { s1 = peg$c3; - peg$currPos++; + peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e4); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f7(); + s1 = peg$f10(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c4) { + if (input.charCodeAt(peg$currPos) === 60) { s1 = peg$c4; - peg$currPos += 2; + peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e5); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f8(); + s1 = peg$f11(); } s0 = s1; } @@ -644,281 +744,205 @@ function peg$parse(input, options) { function peg$parsekey() { var s0, s1; + peg$silentFails++; s0 = peg$currPos; if (input.substr(peg$currPos, 4) === peg$c5) { s1 = peg$c5; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e6); } + if (peg$silentFails === 0) { peg$fail(peg$e7); } } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f9(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; + if (s1 === peg$FAILED) { if (input.substr(peg$currPos, 6) === peg$c6) { s1 = peg$c6; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e7); } + if (peg$silentFails === 0) { peg$fail(peg$e8); } } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f10(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c7) { + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 11) === peg$c7) { s1 = peg$c7; - peg$currPos += 4; + peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e8); } + if (peg$silentFails === 0) { peg$fail(peg$e9); } } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f11(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c8) { + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c8) { s1 = peg$c8; - peg$currPos += 6; + peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e9); } + if (peg$silentFails === 0) { peg$fail(peg$e10); } } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f12(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; + if (s1 === peg$FAILED) { if (input.substr(peg$currPos, 11) === peg$c9) { s1 = peg$c9; peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e10); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f13(); + if (peg$silentFails === 0) { peg$fail(peg$e11); } } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c10) { + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c10) { s1 = peg$c10; - peg$currPos += 2; + peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e11); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f14(); + if (peg$silentFails === 0) { peg$fail(peg$e12); } } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 8) === peg$c11) { + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c11) { s1 = peg$c11; - peg$currPos += 8; + peg$currPos += 7; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e12); } + if (peg$silentFails === 0) { peg$fail(peg$e13); } } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f15(); + } + } + } + } + } + } + if (s1 !== peg$FAILED) { + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + + return s0; + } + + function peg$parsedefaultKey() { + var s0, s1; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c12) { + s1 = peg$c12; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c13) { + s1 = peg$c13; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c14) { + s1 = peg$c14; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 9) === peg$c15) { + s1 = peg$c15; + peg$currPos += 9; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + } + } + } + if (s1 !== peg$FAILED) { + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + + return s0; + } + + function peg$parsemultiselectKey() { + var s0, s1; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c16) { + s1 = peg$c16; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c17) { + s1 = peg$c17; + peg$currPos += 8; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 3) === peg$c18) { + s1 = peg$c18; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e22); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c19) { + s1 = peg$c19; + peg$currPos += 8; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c20) { + s1 = peg$c20; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e24); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 7) === peg$c21) { + s1 = peg$c21; + peg$currPos += 7; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c22) { + s1 = peg$c22; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 8) === peg$c12) { - s1 = peg$c12; - peg$currPos += 8; + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c23) { + s1 = peg$c23; + peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e13); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f16(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 11) === peg$c13) { - s1 = peg$c13; - peg$currPos += 11; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e14); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f17(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c14) { - s1 = peg$c14; - peg$currPos += 4; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e15); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f18(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c15) { - s1 = peg$c15; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e16); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f19(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 8) === peg$c16) { - s1 = peg$c16; - peg$currPos += 8; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e17); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f20(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 3) === peg$c17) { - s1 = peg$c17; - peg$currPos += 3; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e18); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f21(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 7) === peg$c18) { - s1 = peg$c18; - peg$currPos += 7; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e19); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f22(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c19) { - s1 = peg$c19; - peg$currPos += 6; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e20); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f23(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 8) === peg$c20) { - s1 = peg$c20; - peg$currPos += 8; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e21); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f24(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 7) === peg$c21) { - s1 = peg$c21; - peg$currPos += 7; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e22); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f25(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 6) === peg$c22) { - s1 = peg$c22; - peg$currPos += 6; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f26(); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.substr(peg$currPos, 9) === peg$c23) { - s1 = peg$c23; - peg$currPos += 9; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f27(); - } - s0 = s1; - } - } - } - } - } - } - } - } - } - } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } } } @@ -927,6 +951,17 @@ function peg$parse(input, options) { } } } + if (s1 !== peg$FAILED) { + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } return s0; } @@ -953,7 +988,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f28(s1); + s1 = peg$f12(s1); } s0 = s1; @@ -963,13 +998,14 @@ function peg$parse(input, options) { function peg$parsequotedString() { var s0, s1, s2, s3; + peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { s1 = peg$c24; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } if (s1 !== peg$FAILED) { s2 = []; @@ -978,7 +1014,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -987,7 +1023,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } } if (input.charCodeAt(peg$currPos) === 34) { @@ -995,11 +1031,11 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f29(s2); + s0 = peg$f13(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1008,6 +1044,11 @@ function peg$parse(input, options) { peg$currPos = s0; s0 = peg$FAILED; } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } return s0; } @@ -1015,6 +1056,7 @@ function peg$parse(input, options) { function peg$parsealphanumeric() { var s0, s1, s2; + peg$silentFails++; s0 = peg$currPos; s1 = []; s2 = input.charAt(peg$currPos); @@ -1022,7 +1064,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1032,7 +1074,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } } } else { @@ -1040,9 +1082,14 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f30(s1); + s1 = peg$f14(s1); } s0 = s1; + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e31); } + } return s0; } @@ -1050,11 +1097,15 @@ function peg$parse(input, options) { function peg$parselogicalAnd() { var s0, s1; + peg$silentFails++; s0 = peg$currPos; s1 = peg$parse_(); peg$savedPos = s0; - s1 = peg$f31(); + s1 = peg$f15(); s0 = s1; + peg$silentFails--; + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e33); } return s0; } @@ -1069,7 +1120,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1078,12 +1129,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } return s0; } @@ -1096,10 +1147,6 @@ function peg$parse(input, options) { "sortOrder": "desc", }; - function buildFilter(operator, left, right) { - return { operator, left, right }; - } - function applyDefaults(filters) { return { ...defaultValues, @@ -1111,10 +1158,6 @@ function peg$parse(input, options) { defaultValues[field] = value; } - function isDefaultField(field) { - return defaultValues.hasOwnProperty(field); - } - peg$result = peg$startRuleFunction(); if (options.peg$library) { diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index d9ede101f7f8..9abea113b4e2 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -14,6 +14,12 @@ // logicalAnd: rule to match whitespace and return it as a logical 'and' operator // whitespace: rule to match whitespaces +{{ +function buildFilter(operator, left, right) { + return { operator, left, right }; +} +}} + { const defaultValues = { "type": "expense", @@ -22,10 +28,6 @@ "sortOrder": "desc", }; - function buildFilter(operator, left, right) { - return { operator, left, right }; - } - function applyDefaults(filters) { return { ...defaultValues, @@ -36,10 +38,6 @@ function updateDefaultValues(field, value) { defaultValues[field] = value; } - - function isDefaultField(field) { - return defaultValues.hasOwnProperty(field); - } } query @@ -47,82 +45,95 @@ query filterList = head:filter tail:(logicalAnd filter)* { - const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null); - if (!allFilters.length) { - return null; - } - const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") - const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") - if(!nonKeywords.length){ - return keywords.reduce((result, filter) => buildFilter("or", result, filter)) - } - if(!keywords.length){ - return nonKeywords.reduce((result, filter) => buildFilter("and", result, filter)) - } - - return buildFilter("and", keywords.reduce((result, filter) => buildFilter("or", result, filter)), nonKeywords.reduce((result, filter) => buildFilter("and", result, filter))) - - - return allFilters.reduce((result, filter) => buildFilter("and", result, filter)); - } + const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null).filter(Boolean); + if (!allFilters.length) { + return null; + } + + const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") + const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") + + if(!nonKeywords.length){ + return keywords.reduce((result, filter) => buildFilter("or", result, filter)) + } + if(!keywords.length){ + return nonKeywords.reduce((result, filter) => buildFilter("and", result, filter)) + } + + return buildFilter("and", keywords.reduce((result, filter) => buildFilter("or", result, filter)), nonKeywords.reduce((result, filter) => buildFilter("and", result, filter))) + return allFilters.reduce((result, filter) => buildFilter("and", result, filter)); +} + filter - = _ field:key? _ op:operator? _ value:identifier { - if (isDefaultField(field)) { - updateDefaultValues(field, value.trim()); - return null; - } += @(standardFilter / defaultFilter / multiSelectFilter / freeTextFilter ) + +defaultFilter += _ key:defaultKey _ op:operator _ value:identifier { + updateDefaultValues(key, value.trim()); +} - if (!field && !op) { - return buildFilter('eq', 'keyword', value.trim()); - } +freeTextFilter += _ value:identifier _ { + return buildFilter('eq', 'keyword', value.trim()); +} - const values = value.split(','); - const operatorValue = op ?? 'eq'; +multiSelectFilter += _ field:multiselectKey _ op:operator _ value:identifier { + return buildFilter(op, field, value) +} - return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(operatorValue, field, val.trim())), buildFilter(operatorValue, field, values[0])); - } +standardFilter += _ field:key _ op:operator _ value:identifier { + + const values = value.split(','); + return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(op, field, val.trim())), buildFilter(op, field, values[0])); +} operator = (":" / "=") { return "eq"; } / "!=" { return "neq"; } - / ">" { return "gt"; } / ">=" { return "gte"; } - / "<" { return "lt"; } + / ">" { return "gt"; } / "<=" { return "lte"; } + / "<" { return "lt"; } -key - = "type" { return "type"; } - / "status" { return "status"; } - / "date" { return "date"; } - / "amount" { return "amount"; } - / "expenseType" { return "expenseType"; } - / "in" { return "in"; } - / "currency" { return "currency"; } - / "merchant" { return "merchant"; } - / "description" { return "description"; } - / "from" { return "from"; } - / "to" { return "to"; } - / "category" { return "category"; } - / "tag" { return "tag"; } - / "taxRate" { return "taxRate"; } - / "cardID" { return "cardID"; } - / "reportID" { return "reportID"; } - / "keyword" { return "keyword"; } - / "sortBy" { return "sortBy"; } - / "sortOrder" { return "sortOrder"; } +key "key" + = @("date" + / "amount" + / "expenseType" + / "merchant" + / "description" + / "reportID" + / "keyword") + +defaultKey "default key" += @("type" + / "status" + / "sortBy" + / "sortOrder") + +multiselectKey "multiselect key" += @("in" + / "currency" + / "tag" + / "category" + / "to" + / "taxRate" + / "cardID" + / "from") identifier = parts:(quotedString / alphanumeric)+ { return parts.join(''); } -quotedString +quotedString "quote" = '"' chars:[^"\r\n]* '"' { return chars.join(''); } -alphanumeric +alphanumeric "word" = chars:[A-Za-z0-9_@./#&+\-\\',;]+ { return chars.join(''); } -logicalAnd +logicalAnd "and" = _ { return "and"; } _ "whitespace" - = [ \t\r\n]* + = [ \t\r\n]* \ No newline at end of file From b869cfda62ca83bb359d3493e803bda993416d16 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Thu, 22 Aug 2024 13:35:57 +0200 Subject: [PATCH 02/14] grammar clean up and review suggestions --- src/libs/SearchParser/searchParser.js | 103 ++++++++----- src/libs/SearchParser/searchParser.peggy | 185 +++++++++++++---------- 2 files changed, 168 insertions(+), 120 deletions(-) diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index b4fb138f5db3..6fd61ffa21b9 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -4,9 +4,9 @@ -function buildFilter(operator, left, right) { - return { operator, left, right }; -} + function buildFilter(operator, left, right) { + return { operator, left, right }; + } function peg$subclass(child, parent) { function C() { this.constructor = child; } @@ -250,48 +250,71 @@ function peg$parse(input, options) { var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { - const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null).filter(Boolean); - if (!allFilters.length) { - return null; - } - - const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") - const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") - - if(!nonKeywords.length){ - return keywords.reduce((result, filter) => buildFilter("or", result, filter)) - } - if(!keywords.length){ - return nonKeywords.reduce((result, filter) => buildFilter("and", result, filter)) - } + const allFilters = [head, ...tail.map(([_, filter]) => filter)] + .filter((filter) => filter !== null) + .filter(Boolean); + if (!allFilters.length) { + return null; + } - return buildFilter("and", keywords.reduce((result, filter) => buildFilter("or", result, filter)), nonKeywords.reduce((result, filter) => buildFilter("and", result, filter))) + const keywords = allFilters.filter( + (filter) => + filter.left === "keyword" || filter.right?.left === "keyword" + ); + const nonKeywords = allFilters.filter( + (filter) => + filter.left !== "keyword" && filter.right?.left !== "keyword" + ); + + if (!nonKeywords.length) { + return keywords.reduce((result, filter) => + buildFilter("or", result, filter) + ); + } + if (!keywords.length) { + return nonKeywords.reduce((result, filter) => + buildFilter("and", result, filter) + ); + } - return allFilters.reduce((result, filter) => buildFilter("and", result, filter)); -}; + return buildFilter( + "and", + keywords.reduce((result, filter) => buildFilter("or", result, filter)), + nonKeywords.reduce((result, filter) => + buildFilter("and", result, filter) + ) + ); + + return allFilters.reduce((result, filter) => + buildFilter("and", result, filter) + ); + }; var peg$f2 = function(key, op, value) { - updateDefaultValues(key, value.trim()); -}; - var peg$f3 = function(value) { - return buildFilter('eq', 'keyword', value.trim()); -}; + updateDefaultValues(key, value.trim()); + }; + var peg$f3 = function(value) { return buildFilter("eq", "keyword", value.trim()); }; var peg$f4 = function(field, op, value) { - return buildFilter(op, field, value) -}; + return buildFilter(op, field, value); + }; var peg$f5 = function(field, op, value) { - - const values = value.split(','); - return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(op, field, val.trim())), buildFilter(op, field, values[0])); -}; + const values = value.split(","); + return values + .slice(1) + .reduce( + (acc, val) => + buildFilter("or", acc, buildFilter(op, field, val.trim())), + buildFilter(op, field, values[0]) + ); + }; var peg$f6 = function() { return "eq"; }; var peg$f7 = function() { return "neq"; }; var peg$f8 = function() { return "gte"; }; var peg$f9 = function() { return "gt"; }; var peg$f10 = function() { return "lte"; }; var peg$f11 = function() { return "lt"; }; - var peg$f12 = function(parts) { return parts.join(''); }; - var peg$f13 = function(chars) { return chars.join(''); }; - var peg$f14 = function(chars) { return chars.join(''); }; + var peg$f12 = function(parts) { return parts.join(""); }; + var peg$f13 = function(chars) { return chars.join(""); }; + var peg$f14 = function(chars) { return chars.join(""); }; var peg$f15 = function() { return "and"; }; var peg$currPos = options.peg$currPos | 0; var peg$savedPos = peg$currPos; @@ -1141,19 +1164,19 @@ function peg$parse(input, options) { const defaultValues = { - "type": "expense", - "status": "all", - "sortBy": "date", - "sortOrder": "desc", + type: "expense", + status: "all", + sortBy: "date", + sortOrder: "desc", }; function applyDefaults(filters) { return { ...defaultValues, - filters + filters, }; } - + function updateDefaultValues(field, value) { defaultValues[field] = value; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index 9abea113b4e2..797cebe4497c 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -5,90 +5,119 @@ // // query: entry point for the parser and rule to process the values returned by the filterList rule. Takes filters as an argument and returns the final AST output. // filterList: rule to process the array of filters returned by the filter rule. It takes head and tail as arguments, filters it for null values and builds the AST. -// filter: rule to build the filter object. It takes field, operator and value as input and returns {operator, left: field, right: value} or null if the left value is a defaultValues +// filter: abstract rule to simplify the filterList rule.It takes all filter tyepes. +// defaultFilter: rule to process the default values returned by the defaultKey rule. It updates the default values object. +// freeTextFilter: rule to process the free text search values returned by the identifier rule. It builds filter Object. +// multiSelectFilter: rule to process the multi-select values returned by the multiselectKey rule. It builds multiselect filter Object. +// standardFilter: rule to process the values returned by the key rule. It builds filter Object. // operator: rule to match pre-defined search syntax operators, e.g. !=, >, etc -// key: rule to match pre-defined search syntax fields, e.g. amount, merchant, etc +// key: rule to match pre-defined search syntax fields that aren't multi-select, e.g. amount, merchant, etc +// defaultKey: rule to match pre-defined search syntax fields that are used to update default values, e.g. type, status, etc +// multiselectKey: rule to match pre-defined search syntax fields that are used for multi-select filters, e.g. in, currency, etc // identifier: composite rule to match patterns defined by the quotedString and alphanumeric rules // quotedString: rule to match a quoted string pattern, e.g. "this is a quoted string" // alphanumeric: rule to match unquoted alphanumeric characters, e.g. a-z, 0-9, _, @, etc // logicalAnd: rule to match whitespace and return it as a logical 'and' operator // whitespace: rule to match whitespaces +// global initializer (code executed only once) {{ -function buildFilter(operator, left, right) { - return { operator, left, right }; -} + function buildFilter(operator, left, right) { + return { operator, left, right }; + } }} +// per-parser initializer (code executed before every parse) { const defaultValues = { - "type": "expense", - "status": "all", - "sortBy": "date", - "sortOrder": "desc", + type: "expense", + status: "all", + sortBy: "date", + sortOrder: "desc", }; function applyDefaults(filters) { return { ...defaultValues, - filters + filters, }; } - + function updateDefaultValues(field, value) { defaultValues[field] = value; } } -query - = _ filters:filterList? _ { return applyDefaults(filters); } +query = _ filters:filterList? _ { return applyDefaults(filters); } filterList = head:filter tail:(logicalAnd filter)* { - const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null).filter(Boolean); - if (!allFilters.length) { - return null; - } - - const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") - const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") - - if(!nonKeywords.length){ - return keywords.reduce((result, filter) => buildFilter("or", result, filter)) - } - if(!keywords.length){ - return nonKeywords.reduce((result, filter) => buildFilter("and", result, filter)) - } - - return buildFilter("and", keywords.reduce((result, filter) => buildFilter("or", result, filter)), nonKeywords.reduce((result, filter) => buildFilter("and", result, filter))) - - return allFilters.reduce((result, filter) => buildFilter("and", result, filter)); -} - -filter -= @(standardFilter / defaultFilter / multiSelectFilter / freeTextFilter ) + const allFilters = [head, ...tail.map(([_, filter]) => filter)] + .filter((filter) => filter !== null) + .filter(Boolean); + if (!allFilters.length) { + return null; + } + + const keywords = allFilters.filter( + (filter) => + filter.left === "keyword" || filter.right?.left === "keyword" + ); + const nonKeywords = allFilters.filter( + (filter) => + filter.left !== "keyword" && filter.right?.left !== "keyword" + ); + + if (!nonKeywords.length) { + return keywords.reduce((result, filter) => + buildFilter("or", result, filter) + ); + } + if (!keywords.length) { + return nonKeywords.reduce((result, filter) => + buildFilter("and", result, filter) + ); + } + + return buildFilter( + "and", + keywords.reduce((result, filter) => buildFilter("or", result, filter)), + nonKeywords.reduce((result, filter) => + buildFilter("and", result, filter) + ) + ); + + return allFilters.reduce((result, filter) => + buildFilter("and", result, filter) + ); + } + +filter = @(standardFilter / defaultFilter / multiSelectFilter / freeTextFilter) defaultFilter -= _ key:defaultKey _ op:operator _ value:identifier { - updateDefaultValues(key, value.trim()); -} + = _ key:defaultKey _ op:operator _ value:identifier { + updateDefaultValues(key, value.trim()); + } freeTextFilter -= _ value:identifier _ { - return buildFilter('eq', 'keyword', value.trim()); -} + = _ value:identifier _ { return buildFilter("eq", "keyword", value.trim()); } multiSelectFilter -= _ field:multiselectKey _ op:operator _ value:identifier { - return buildFilter(op, field, value) -} + = _ field:multiselectKey _ op:operator _ value:identifier { + return buildFilter(op, field, value); + } standardFilter -= _ field:key _ op:operator _ value:identifier { - - const values = value.split(','); - return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(op, field, val.trim())), buildFilter(op, field, values[0])); -} + = _ field:key _ op:operator _ value:identifier { + const values = value.split(","); + return values + .slice(1) + .reduce( + (acc, val) => + buildFilter("or", acc, buildFilter(op, field, val.trim())), + buildFilter(op, field, values[0]) + ); + } operator = (":" / "=") { return "eq"; } @@ -99,41 +128,37 @@ operator / "<" { return "lt"; } key "key" - = @("date" - / "amount" - / "expenseType" - / "merchant" - / "description" - / "reportID" - / "keyword") - -defaultKey "default key" -= @("type" - / "status" - / "sortBy" - / "sortOrder") - + = @( + "date" + / "amount" + / "expenseType" + / "merchant" + / "description" + / "reportID" + / "keyword" + ) + +defaultKey "default key" = @("type" / "status" / "sortBy" / "sortOrder") + multiselectKey "multiselect key" -= @("in" - / "currency" - / "tag" - / "category" - / "to" - / "taxRate" - / "cardID" - / "from") + = @( + "in" + / "currency" + / "tag" + / "category" + / "to" + / "taxRate" + / "cardID" + / "from" + ) -identifier - = parts:(quotedString / alphanumeric)+ { return parts.join(''); } +identifier = parts:(quotedString / alphanumeric)+ { return parts.join(""); } -quotedString "quote" - = '"' chars:[^"\r\n]* '"' { return chars.join(''); } +quotedString "quote" = "\"" chars:[^"\r\n]* "\"" { return chars.join(""); } alphanumeric "word" - = chars:[A-Za-z0-9_@./#&+\-\\',;]+ { return chars.join(''); } + = chars:[A-Za-z0-9_@./#&+\-\\',;]+ { return chars.join(""); } -logicalAnd "and" - = _ { return "and"; } +logicalAnd "and" = _ { return "and"; } -_ "whitespace" - = [ \t\r\n]* \ No newline at end of file +_ "whitespace" = [ \t\r\n]* From 6d0f448e8e24e7c2507760e8af498b967a87bf7c Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 23 Aug 2024 11:05:28 +0200 Subject: [PATCH 03/14] correct typos --- src/libs/SearchParser/searchParser.peggy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index 797cebe4497c..c6ebc286e635 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -5,7 +5,7 @@ // // query: entry point for the parser and rule to process the values returned by the filterList rule. Takes filters as an argument and returns the final AST output. // filterList: rule to process the array of filters returned by the filter rule. It takes head and tail as arguments, filters it for null values and builds the AST. -// filter: abstract rule to simplify the filterList rule.It takes all filter tyepes. +// filter: abstract rule to simplify the filterList rule. It takes all filter types. // defaultFilter: rule to process the default values returned by the defaultKey rule. It updates the default values object. // freeTextFilter: rule to process the free text search values returned by the identifier rule. It builds filter Object. // multiSelectFilter: rule to process the multi-select values returned by the multiselectKey rule. It builds multiselect filter Object. From d9590f3d6fa6d5d35d08d5cd138c376a511064a1 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 23 Aug 2024 15:16:27 +0200 Subject: [PATCH 04/14] Add keyword advanced filter for Search --- src/CONST.ts | 6 ++ src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + src/languages/en.ts | 2 + src/languages/es.ts | 2 + .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/SearchParser/searchParser.js | 57 +++++++++------ src/libs/SearchParser/searchParser.peggy | 1 + src/libs/SearchUtils.ts | 3 +- src/pages/Search/AdvancedSearchFilters.tsx | 6 ++ .../SearchFiltersHasPage.tsx | 73 +++++++++++++++++++ src/types/form/SearchAdvancedFiltersForm.ts | 4 + 13 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 93d2921e704f..b72dc2dfe796 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5267,6 +5267,7 @@ const CONST = { EXPENSE: 'expense', INVOICE: 'invoice', TRIP: 'trip', + CHAT: 'chat', }, ACTION_TYPES: { VIEW: 'view', @@ -5309,6 +5310,10 @@ const CONST = { PAID: 'paid', }, }, + CHAT_TYPES: { + LINK: 'link', + ATTACHMENT: 'attachment', + }, TAB: { EXPENSE: { ALL: 'type:expense status:all', @@ -5362,6 +5367,7 @@ const CONST = { CARD_ID: 'cardID', REPORT_ID: 'reportID', KEYWORD: 'keyword', + HAS: 'has', }, }, diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 73271d85ea49..419211808149 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -53,6 +53,7 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_TAG: 'search/filters/tag', SEARCH_ADVANCED_FILTERS_FROM: 'search/filters/from', SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', + SEARCH_ADVANCED_FILTERS_HAS: 'search/filters/has', SEARCH_REPORT: { route: 'search/view/:reportID', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 142b2f80a66e..8589f4ffd60b 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -46,6 +46,7 @@ const SCREENS = { ADVANCED_FILTERS_TAG_RHP: 'Search_Advanced_Filters_Tag_RHP', ADVANCED_FILTERS_FROM_RHP: 'Search_Advanced_Filters_From_RHP', ADVANCED_FILTERS_TO_RHP: 'Search_Advanced_Filters_To_RHP', + ADVANCED_FILTERS_HAS_RHP: 'Search_Advanced_Filters_Has_RHP', TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP', BOTTOM_TAB: 'Search_Bottom_Tab', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index f5d441923344..374a803dbeb1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3750,6 +3750,8 @@ export default { keyword: 'Keyword', hasKeywords: 'Has keywords', currency: 'Currency', + has: 'Has', + link: 'Link', amount: { lessThan: (amount?: string) => `Less than ${amount ?? ''}`, greaterThan: (amount?: string) => `Greater than ${amount ?? ''}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index ca84c83d16e1..b35dc8b1a40a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3800,6 +3800,8 @@ export default { keyword: 'Palabra clave', hasKeywords: 'Tiene palabras clave', currency: 'Divisa', + has: 'Tiene', + link: 'Enlace', amount: { lessThan: (amount?: string) => `Menos de ${amount ?? ''}`, greaterThan: (amount?: string) => `Más que ${amount ?? ''}`, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 4694a2e73d5c..a6c242d1025e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -528,6 +528,7 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator require('../../../../pages/Search/SearchFiltersTagPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_FROM_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersFromPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersToPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage').default, }); const RestrictedActionModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index bb9d92c7a5a3..3e4590d7a5a8 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1038,6 +1038,7 @@ const config: LinkingOptions['config'] = { [SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, [SCREENS.SEARCH.ADVANCED_FILTERS_FROM_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, [SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + [SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, }, }, [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: { diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 6fd61ffa21b9..ac3efc19318f 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -204,7 +204,8 @@ function peg$parse(input, options) { var peg$c21 = "taxRate"; var peg$c22 = "cardID"; var peg$c23 = "from"; - var peg$c24 = "\""; + var peg$c24 = "has"; + var peg$c25 = "\""; var peg$r0 = /^[:=]/; var peg$r1 = /^[^"\r\n]/; @@ -239,14 +240,15 @@ function peg$parse(input, options) { var peg$e25 = peg$literalExpectation("taxRate", false); var peg$e26 = peg$literalExpectation("cardID", false); var peg$e27 = peg$literalExpectation("from", false); - var peg$e28 = peg$otherExpectation("quote"); - var peg$e29 = peg$literalExpectation("\"", false); - var peg$e30 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e31 = peg$otherExpectation("word"); - var peg$e32 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); - var peg$e33 = peg$otherExpectation("and"); - var peg$e34 = peg$otherExpectation("whitespace"); - var peg$e35 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e28 = peg$literalExpectation("has", false); + var peg$e29 = peg$otherExpectation("quote"); + var peg$e30 = peg$literalExpectation("\"", false); + var peg$e31 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e32 = peg$otherExpectation("word"); + var peg$e33 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); + var peg$e34 = peg$otherExpectation("and"); + var peg$e35 = peg$otherExpectation("whitespace"); + var peg$e36 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { @@ -967,6 +969,15 @@ function peg$parse(input, options) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e27); } } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 3) === peg$c24) { + s1 = peg$c24; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + } } } } @@ -1024,11 +1035,11 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c24; + s1 = peg$c25; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1037,7 +1048,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1046,15 +1057,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c24; + s3 = peg$c25; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; @@ -1070,7 +1081,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } return s0; @@ -1087,7 +1098,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1097,7 +1108,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } } } else { @@ -1111,7 +1122,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } return s0; @@ -1128,7 +1139,7 @@ function peg$parse(input, options) { s0 = s1; peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } return s0; } @@ -1143,7 +1154,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e36); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1152,12 +1163,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e36); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } return s0; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index c6ebc286e635..775b97d1a214 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -150,6 +150,7 @@ multiselectKey "multiselect key" / "taxRate" / "cardID" / "from" + / "has" ) identifier = parts:(quotedString / alphanumeric)+ { return parts.join(""); } diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index ce2429c5653b..c2013323ae9c 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -502,7 +502,8 @@ function buildQueryStringFromFilters(filterValues: Partial 0 ) { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 5143a2d70008..8354234be54b 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -209,6 +209,12 @@ function AdvancedSearchFilters() { description: 'common.to' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS, translate), + description: 'search.filters.has' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, + shouldHide: searchAdvancedFilters?.type !== CONST.SEARCH.DATA_TYPES.CHAT, + }, ], [searchAdvancedFilters, translate, cardList, taxRates, personalDetails], ); diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx new file mode 100644 index 000000000000..3a835553bcb1 --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx @@ -0,0 +1,73 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SearchMultipleSelectionPicker from '@components/Search/SearchMultipleSelectionPicker'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import * as SearchActions from '@userActions/Search'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; + +type HasItem = { + name: string; + value: 'attachment' | 'link'; +}; + +function SearchFiltersHasPage() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + + const hasItems: HasItem[] = useMemo( + () => [ + { + name: translate('common.attachment'), + value: CONST.SEARCH.CHAT_TYPES.ATTACHMENT, + }, + { + name: translate('search.filters.link'), + value: CONST.SEARCH.CHAT_TYPES.LINK, + }, + ], + [translate], + ); + + const selectedHasItems = searchAdvancedFiltersForm?.has?.map((value) => hasItems.find((hasItem) => hasItem.value === value)).filter((item): item is HasItem => item !== undefined) ?? []; + + const updateHasFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({has: values}), []); + + return ( + + { + Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); + }} + /> + + + + + ); +} + +SearchFiltersHasPage.displayName = 'SearchFiltersHasPage'; + +export default SearchFiltersHasPage; diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index 6541072cae81..0b9ea53596ed 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -2,6 +2,7 @@ import type {ValueOf} from 'type-fest'; import type Form from './Form'; const FILTER_KEYS = { + TYPE: 'type', DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', CURRENCY: 'currency', @@ -19,6 +20,7 @@ const FILTER_KEYS = { KEYWORD: 'keyword', FROM: 'from', TO: 'to', + HAS: 'has', } as const; type InputID = ValueOf; @@ -26,6 +28,7 @@ type InputID = ValueOf; type SearchAdvancedFiltersForm = Form< InputID, { + [FILTER_KEYS.TYPE]: string; [FILTER_KEYS.DATE_AFTER]: string; [FILTER_KEYS.DATE_BEFORE]: string; [FILTER_KEYS.CURRENCY]: string[]; @@ -43,6 +46,7 @@ type SearchAdvancedFiltersForm = Form< [FILTER_KEYS.TAG]: string[]; [FILTER_KEYS.FROM]: string[]; [FILTER_KEYS.TO]: string[]; + [FILTER_KEYS.HAS]: string[]; } >; From 879ec34d5418b5eb1da37d7039edcb89e418dcd6 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Mon, 26 Aug 2024 12:55:30 +0200 Subject: [PATCH 05/14] fix empty selected items on native --- .../Search/SearchMultipleSelectionPicker.tsx | 6 +++++- .../SearchFiltersExpenseTypePage.tsx | 14 +++++++++----- .../SearchFiltersHasPage.tsx | 4 +++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index 57003110fd1a..558b89715b61 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import Button from '@components/Button'; import SelectionList from '@components/SelectionList'; import SelectableListItem from '@components/SelectionList/SelectableListItem'; @@ -28,6 +28,10 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedItems, setSelectedItems] = useState(initiallySelectedItems ?? []); + useEffect(() => { + setSelectedItems(initiallySelectedItems ?? []); + }, [initiallySelectedItems]); + const {sections, noResultsFound} = useMemo(() => { const selectedItemsSection = selectedItems .filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx index 96984029142b..65d474b41905 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx @@ -19,10 +19,14 @@ function SearchFiltersExpenseTypePage() { const {translate} = useLocalize(); const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const selectedExpenseTypes = searchAdvancedFiltersForm?.expenseType?.map((expenseType) => { - const expenseTypeName = translate(getExpenseTypeTranslationKey(expenseType as ValueOf)); - return {name: expenseTypeName, value: expenseType}; - }); + const initiallySelectedItems = useMemo( + () => + searchAdvancedFiltersForm?.expenseType?.map((expenseType) => { + const expenseTypeName = translate(getExpenseTypeTranslationKey(expenseType as ValueOf)); + return {name: expenseTypeName, value: expenseType}; + }), + [searchAdvancedFiltersForm, translate], + ); const allExpenseTypes = Object.values(CONST.SEARCH.TRANSACTION_TYPE); const expenseTypesItems = useMemo(() => { @@ -52,7 +56,7 @@ function SearchFiltersExpenseTypePage() { diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx index 3a835553bcb1..58210f8336b3 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx @@ -37,7 +37,9 @@ function SearchFiltersHasPage() { [translate], ); - const selectedHasItems = searchAdvancedFiltersForm?.has?.map((value) => hasItems.find((hasItem) => hasItem.value === value)).filter((item): item is HasItem => item !== undefined) ?? []; + const selectedHasItems = useMemo(() => { + return searchAdvancedFiltersForm?.has?.map((value) => hasItems.find((hasItem) => hasItem.value === value)).filter((item): item is HasItem => item !== undefined) ?? []; + }, [searchAdvancedFiltersForm, hasItems]); const updateHasFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({has: values}), []); From 101ed2e4275fea52327656e3fa48ecb46cf25d49 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Mon, 26 Aug 2024 17:28:08 +0200 Subject: [PATCH 06/14] review suggestions --- .../SearchFiltersHasPage.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx index 58210f8336b3..e964554373b0 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx @@ -12,9 +12,9 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -type HasItem = { +type FilterItem = { name: string; - value: 'attachment' | 'link'; + value: typeof CONST.SEARCH.CHAT_TYPES.ATTACHMENT | typeof CONST.SEARCH.CHAT_TYPES.LINK; }; function SearchFiltersHasPage() { @@ -23,7 +23,7 @@ function SearchFiltersHasPage() { const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const hasItems: HasItem[] = useMemo( + const filterItems: FilterItem[] = useMemo( () => [ { name: translate('common.attachment'), @@ -37,9 +37,9 @@ function SearchFiltersHasPage() { [translate], ); - const selectedHasItems = useMemo(() => { - return searchAdvancedFiltersForm?.has?.map((value) => hasItems.find((hasItem) => hasItem.value === value)).filter((item): item is HasItem => item !== undefined) ?? []; - }, [searchAdvancedFiltersForm, hasItems]); + const selectedOptions = useMemo(() => { + return searchAdvancedFiltersForm?.has?.map((value) => filterItems.find((filterItem) => filterItem.value === value)).filter((item): item is FilterItem => item !== undefined) ?? []; + }, [searchAdvancedFiltersForm, filterItems]); const updateHasFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({has: values}), []); @@ -60,8 +60,8 @@ function SearchFiltersHasPage() { From 1331d533844a226091045762129c16c3e3f7c402 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Tue, 27 Aug 2024 11:34:22 +0200 Subject: [PATCH 07/14] add shouldShow to filters --- src/pages/Search/AdvancedSearchFilters.tsx | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 83f3aaf8eef5..4dc848f035bf 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -136,87 +136,104 @@ function AdvancedSearchFilters() { const taxRates = getAllTaxRates(); const personalDetails = usePersonalDetails(); + const currentType = searchAdvancedFilters.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE; + const shouldShowExpenseFilter = currentType === CONST.SEARCH.DATA_TYPES.EXPENSE; + const shouldShowChatFilter = currentType === CONST.SEARCH.DATA_TYPES.CHAT; + const advancedFilters = useMemo( () => [ { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), description: 'common.date' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, + shouldShow: true, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate), description: 'common.currency' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, + shouldShow: shouldShowExpenseFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate), description: 'common.merchant' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, + shouldShow: shouldShowExpenseFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, translate), description: 'common.description' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, + shouldShow: true, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, translate), description: 'common.reportID' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, + shouldShow: true, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate), description: 'common.total' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, + shouldShow: shouldShowExpenseFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate), description: 'common.category' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, + shouldShow: shouldShowExpenseFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), description: 'search.filters.hasKeywords' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, + shouldShow: true, }, { title: getFilterCardDisplayTitle(searchAdvancedFilters, cardList), description: 'common.card' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, - shouldHide: Object.keys(cardList).length === 0, + shouldShow: shouldShowExpenseFilter && Object.keys(cardList).length !== 0, }, { title: getFilterTaxRateDisplayTitle(searchAdvancedFilters, taxRates), description: 'workspace.taxes.taxRate' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, + shouldShow: shouldShowExpenseFilter, }, { title: getExpenseTypeDisplayTitle(searchAdvancedFilters, translate), description: 'search.expenseType' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, + shouldShow: shouldShowExpenseFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, translate), description: 'common.tag' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, + shouldShow: shouldShowExpenseFilter, }, { title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), description: 'common.from' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, + shouldShow: true, }, { title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), description: 'common.to' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + shouldShow: true, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS, translate), description: 'search.filters.has' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - shouldHide: searchAdvancedFilters?.type !== CONST.SEARCH.DATA_TYPES.CHAT, + shouldShow: shouldShowChatFilter, }, ], - [searchAdvancedFilters, translate, cardList, taxRates, personalDetails], + [searchAdvancedFilters, translate, shouldShowExpenseFilter, cardList, taxRates, personalDetails, shouldShowChatFilter], ); const onFormSubmit = () => { @@ -236,7 +253,7 @@ function AdvancedSearchFilters() { {advancedFilters.map((item) => { const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); - if (item.shouldHide) { + if (!item.shouldShow) { return undefined; } return ( From ffd9a881a35f36f6aa709ed6ecdbafee5c8bb9d8 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Tue, 27 Aug 2024 11:57:08 +0200 Subject: [PATCH 08/14] fix variable names --- src/pages/Search/AdvancedSearchFilters.tsx | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 4dc848f035bf..f1573b3e5c58 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -137,8 +137,8 @@ function AdvancedSearchFilters() { const personalDetails = usePersonalDetails(); const currentType = searchAdvancedFilters.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE; - const shouldShowExpenseFilter = currentType === CONST.SEARCH.DATA_TYPES.EXPENSE; - const shouldShowChatFilter = currentType === CONST.SEARCH.DATA_TYPES.CHAT; + const shouldShowExpenseSpecificFilter = currentType === CONST.SEARCH.DATA_TYPES.EXPENSE; + const shouldShowChatSpecificFilter = currentType === CONST.SEARCH.DATA_TYPES.CHAT; const advancedFilters = useMemo( () => [ @@ -152,13 +152,13 @@ function AdvancedSearchFilters() { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate), description: 'common.currency' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate), description: 'common.merchant' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, translate), @@ -176,13 +176,13 @@ function AdvancedSearchFilters() { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate), description: 'common.total' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate), description: 'common.category' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), @@ -194,25 +194,25 @@ function AdvancedSearchFilters() { title: getFilterCardDisplayTitle(searchAdvancedFilters, cardList), description: 'common.card' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, - shouldShow: shouldShowExpenseFilter && Object.keys(cardList).length !== 0, + shouldShow: shouldShowExpenseSpecificFilter && Object.keys(cardList).length !== 0, }, { title: getFilterTaxRateDisplayTitle(searchAdvancedFilters, taxRates), description: 'workspace.taxes.taxRate' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getExpenseTypeDisplayTitle(searchAdvancedFilters, translate), description: 'search.expenseType' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, translate), description: 'common.tag' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, - shouldShow: shouldShowExpenseFilter, + shouldShow: shouldShowExpenseSpecificFilter, }, { title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), @@ -230,10 +230,10 @@ function AdvancedSearchFilters() { title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS, translate), description: 'search.filters.has' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - shouldShow: shouldShowChatFilter, + shouldShow: shouldShowChatSpecificFilter, }, ], - [searchAdvancedFilters, translate, shouldShowExpenseFilter, cardList, taxRates, personalDetails, shouldShowChatFilter], + [searchAdvancedFilters, translate, shouldShowExpenseSpecificFilter, cardList, taxRates, personalDetails, shouldShowChatSpecificFilter], ); const onFormSubmit = () => { From 9ec3a4cb77527f8562ce6b99a8d12a50d3f253fe Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 28 Aug 2024 14:33:13 +0200 Subject: [PATCH 09/14] add getFilterHasDisplayTitle --- src/libs/SearchUtils.ts | 11 +++++++++++ src/pages/Search/AdvancedSearchFilters.tsx | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 370114b09f8c..339cab6723a6 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -436,6 +436,16 @@ function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths { + // eslint-disable-next-line default-case + switch (has) { + case CONST.SEARCH.CHAT_TYPES.LINK: + return 'search.filters.link'; + case CONST.SEARCH.CHAT_TYPES.ATTACHMENT: + return 'common.attachment'; + } +} + /** * Given object with chosen search filters builds correct query string from them */ @@ -572,4 +582,5 @@ export { shouldShowYear, buildCannedSearchQuery, getExpenseTypeTranslationKey, + getChatFiltersTranslationKey, }; diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index f1573b3e5c58..3af6d82db330 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -125,6 +125,16 @@ function getExpenseTypeDisplayTitle(filters: Partial, : undefined; } +function getFilterHasDisplayTitle(filters: Partial, translate: LocaleContextProps['translate']) { + const filterValue = filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS]; + return filterValue + ? Object.values(CONST.SEARCH.CHAT_TYPES) + .filter((hasFilter) => filterValue.includes(hasFilter)) + .map((hasFilter) => translate(SearchUtils.getChatFiltersTranslationKey(hasFilter))) + .join(', ') + : undefined; +} + function AdvancedSearchFilters() { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -227,10 +237,10 @@ function AdvancedSearchFilters() { shouldShow: true, }, { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS, translate), + title: getFilterHasDisplayTitle(searchAdvancedFilters, translate), description: 'search.filters.has' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - shouldShow: shouldShowChatSpecificFilter, + shouldShow: true, }, ], [searchAdvancedFilters, translate, shouldShowExpenseSpecificFilter, cardList, taxRates, personalDetails, shouldShowChatSpecificFilter], From ebd2685ba59de751e877787c59eb88346d744e5c Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 28 Aug 2024 14:34:00 +0200 Subject: [PATCH 10/14] fix bug --- src/pages/Search/AdvancedSearchFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 3af6d82db330..0f7a6effc9c4 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -240,7 +240,7 @@ function AdvancedSearchFilters() { title: getFilterHasDisplayTitle(searchAdvancedFilters, translate), description: 'search.filters.has' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - shouldShow: true, + shouldShow: shouldShowChatSpecificFilter, }, ], [searchAdvancedFilters, translate, shouldShowExpenseSpecificFilter, cardList, taxRates, personalDetails, shouldShowChatSpecificFilter], From efe98a3650701010af6292cbc008fa2b4ec5d428 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 28 Aug 2024 16:09:03 +0200 Subject: [PATCH 11/14] add filter arrays --- src/pages/Search/AdvancedSearchFilters.tsx | 227 ++++++++++++--------- 1 file changed, 129 insertions(+), 98 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 0f7a6effc9c4..6d2bc034da2e 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -19,11 +19,20 @@ import {getAllTaxRates} from '@libs/PolicyUtils'; import * as SearchUtils from '@libs/SearchUtils'; import * as SearchActions from '@userActions/Search'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {CardList, PersonalDetailsList} from '@src/types/onyx'; +type AdvancedFilter = { + title: string | undefined; + description: TranslationPaths; + route: Route; + shouldHide?: boolean; +}; + function getFilterCardDisplayTitle(filters: Partial, cards: CardList) { const filterValue = filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID]; return filterValue @@ -146,106 +155,128 @@ function AdvancedSearchFilters() { const taxRates = getAllTaxRates(); const personalDetails = usePersonalDetails(); - const currentType = searchAdvancedFilters.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE; - const shouldShowExpenseSpecificFilter = currentType === CONST.SEARCH.DATA_TYPES.EXPENSE; - const shouldShowChatSpecificFilter = currentType === CONST.SEARCH.DATA_TYPES.CHAT; + const expenseFilters = useMemo( + () => + [ + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), + description: 'common.date' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate), + description: 'common.currency' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate), + description: 'common.merchant' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, translate), + description: 'common.description' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, translate), + description: 'common.reportID' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate), + description: 'common.total' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate), + description: 'common.category' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), + description: 'search.filters.hasKeywords' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, + }, + { + title: getFilterCardDisplayTitle(searchAdvancedFilters, cardList), + description: 'common.card' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, + shouldHide: Object.keys(cardList).length === 0, + }, + { + title: getFilterTaxRateDisplayTitle(searchAdvancedFilters, taxRates), + description: 'workspace.taxes.taxRate' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, + }, + { + title: getExpenseTypeDisplayTitle(searchAdvancedFilters, translate), + description: 'search.expenseType' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, translate), + description: 'common.tag' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, + }, + { + title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), + description: 'common.from' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, + }, + { + title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), + description: 'common.to' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + }, + ] as AdvancedFilter[], + [translate, cardList, taxRates, personalDetails, searchAdvancedFilters], + ); - const advancedFilters = useMemo( - () => [ - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), - description: 'common.date' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, - shouldShow: true, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate), - description: 'common.currency' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate), - description: 'common.merchant' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, translate), - description: 'common.description' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, - shouldShow: true, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, translate), - description: 'common.reportID' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, - shouldShow: true, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate), - description: 'common.total' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate), - description: 'common.category' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), - description: 'search.filters.hasKeywords' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, - shouldShow: true, - }, - { - title: getFilterCardDisplayTitle(searchAdvancedFilters, cardList), - description: 'common.card' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, - shouldShow: shouldShowExpenseSpecificFilter && Object.keys(cardList).length !== 0, - }, - { - title: getFilterTaxRateDisplayTitle(searchAdvancedFilters, taxRates), - description: 'workspace.taxes.taxRate' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getExpenseTypeDisplayTitle(searchAdvancedFilters, translate), - description: 'search.expenseType' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, translate), - description: 'common.tag' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, - shouldShow: shouldShowExpenseSpecificFilter, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), - description: 'common.from' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, - shouldShow: true, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), - description: 'common.to' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, - shouldShow: true, - }, - { - title: getFilterHasDisplayTitle(searchAdvancedFilters, translate), - description: 'search.filters.has' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - shouldShow: shouldShowChatSpecificFilter, - }, - ], - [searchAdvancedFilters, translate, shouldShowExpenseSpecificFilter, cardList, taxRates, personalDetails, shouldShowChatSpecificFilter], + const chatFilters = useMemo( + () => + [ + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), + description: 'search.filters.hasKeywords' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, + }, + { + title: getFilterHasDisplayTitle(searchAdvancedFilters, translate), + description: 'search.filters.has' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, + }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), + description: 'common.date' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, + }, + { + title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), + description: 'common.from' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, + }, + { + title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), + description: 'common.to' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + }, + ] as AdvancedFilter[], + [searchAdvancedFilters, translate, personalDetails], ); + const selectFilters = (type: string) => { + switch (type) { + case CONST.SEARCH.DATA_TYPES.CHAT: + return chatFilters; + case CONST.SEARCH.DATA_TYPES.EXPENSE: + default: + return expenseFilters; + } + }; + + const advancedFilters = selectFilters(searchAdvancedFilters?.type); + const onFormSubmit = () => { const query = SearchUtils.buildQueryStringFromFilters(searchAdvancedFilters); SearchActions.clearAdvancedFilters(); @@ -263,7 +294,7 @@ function AdvancedSearchFilters() { {advancedFilters.map((item) => { const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); - if (!item.shouldShow) { + if (item.shouldHide) { return undefined; } return ( From 65e444f869253957c93f9851feb99c9465c892d1 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Thu, 29 Aug 2024 15:35:58 +0200 Subject: [PATCH 12/14] improve advaanced filter page --- src/pages/Search/AdvancedSearchFilters.tsx | 262 ++++++++++----------- 1 file changed, 124 insertions(+), 138 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 6d2bc034da2e..526e65154f38 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -1,6 +1,7 @@ import {Str} from 'expensify-common'; -import React, {useMemo} from 'react'; +import React from 'react'; import {View} from 'react-native'; +import type {ValueOf} from 'react-native-gesture-handler/lib/typescript/typeUtils'; import {useOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; @@ -19,18 +20,95 @@ import {getAllTaxRates} from '@libs/PolicyUtils'; import * as SearchUtils from '@libs/SearchUtils'; import * as SearchActions from '@userActions/Search'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {CardList, PersonalDetailsList} from '@src/types/onyx'; -type AdvancedFilter = { - title: string | undefined; - description: TranslationPaths; - route: Route; - shouldHide?: boolean; +const baseFilterConfig = { + date: { + title: getFilterDisplayTitle, + description: 'common.date' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, + }, + currency: { + title: getFilterDisplayTitle, + description: 'common.currency' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, + }, + merchant: { + title: getFilterDisplayTitle, + description: 'common.merchant' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, + }, + description: { + title: getFilterDisplayTitle, + description: 'common.description' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, + }, + reportID: { + title: getFilterDisplayTitle, + description: 'common.reportID' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, + }, + amount: { + title: getFilterDisplayTitle, + description: 'common.total' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, + }, + category: { + title: getFilterDisplayTitle, + description: 'common.category' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, + }, + keyword: { + title: getFilterDisplayTitle, + description: 'search.filters.hasKeywords' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, + }, + cardID: { + title: getFilterCardDisplayTitle, + description: 'common.card' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, + shouldHide: (cards: CardList) => Object.keys(cards).length === 0, + }, + taxRate: { + title: getFilterTaxRateDisplayTitle, + description: 'workspace.taxes.taxRate' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, + }, + expenseType: { + title: getExpenseTypeDisplayTitle, + description: 'search.expenseType' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, + }, + tag: { + title: getFilterDisplayTitle, + description: 'common.tag' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, + }, + from: { + title: getFilterParticipantDisplayTitle, + description: 'common.from' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, + }, + to: { + title: getFilterParticipantDisplayTitle, + description: 'common.to' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + }, + has: { + title: getFilterHasDisplayTitle, + description: 'search.filters.has' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, + }, +}; + +const typeFiltersKeys: Record>> = { + [CONST.SEARCH.DATA_TYPES.EXPENSE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'expenseType', 'tag', 'from', 'to'], + [CONST.SEARCH.DATA_TYPES.INVOICE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to'], + [CONST.SEARCH.DATA_TYPES.TRIP]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to'], + [CONST.SEARCH.DATA_TYPES.CHAT]: ['date', 'keyword', 'from', 'to', 'has'], }; function getFilterCardDisplayTitle(filters: Partial, cards: CardList) { @@ -155,127 +233,7 @@ function AdvancedSearchFilters() { const taxRates = getAllTaxRates(); const personalDetails = usePersonalDetails(); - const expenseFilters = useMemo( - () => - [ - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), - description: 'common.date' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate), - description: 'common.currency' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate), - description: 'common.merchant' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, translate), - description: 'common.description' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, translate), - description: 'common.reportID' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate), - description: 'common.total' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate), - description: 'common.category' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), - description: 'search.filters.hasKeywords' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, - }, - { - title: getFilterCardDisplayTitle(searchAdvancedFilters, cardList), - description: 'common.card' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, - shouldHide: Object.keys(cardList).length === 0, - }, - { - title: getFilterTaxRateDisplayTitle(searchAdvancedFilters, taxRates), - description: 'workspace.taxes.taxRate' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, - }, - { - title: getExpenseTypeDisplayTitle(searchAdvancedFilters, translate), - description: 'search.expenseType' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, translate), - description: 'common.tag' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), - description: 'common.from' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), - description: 'common.to' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, - }, - ] as AdvancedFilter[], - [translate, cardList, taxRates, personalDetails, searchAdvancedFilters], - ); - - const chatFilters = useMemo( - () => - [ - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), - description: 'search.filters.hasKeywords' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, - }, - { - title: getFilterHasDisplayTitle(searchAdvancedFilters, translate), - description: 'search.filters.has' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), - description: 'common.date' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), - description: 'common.from' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), - description: 'common.to' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, - }, - ] as AdvancedFilter[], - [searchAdvancedFilters, translate, personalDetails], - ); - - const selectFilters = (type: string) => { - switch (type) { - case CONST.SEARCH.DATA_TYPES.CHAT: - return chatFilters; - case CONST.SEARCH.DATA_TYPES.EXPENSE: - default: - return expenseFilters; - } - }; - - const advancedFilters = selectFilters(searchAdvancedFilters?.type); + const currentType = searchAdvancedFilters?.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE; const onFormSubmit = () => { const query = SearchUtils.buildQueryStringFromFilters(searchAdvancedFilters); @@ -288,22 +246,50 @@ function AdvancedSearchFilters() { ); }; + const filters = typeFiltersKeys[currentType].map((key) => { + const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(baseFilterConfig[key].route))); + let filterTitle; + if ( + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG + ) { + filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, key, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID) { + filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, cardList); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE) { + filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, taxRates); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS) { + filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO) { + filterTitle = baseFilterConfig[key].title(searchAdvancedFilters[key] ?? [], personalDetails); + } + return { + key, + title: filterTitle, + description: translate(baseFilterConfig[key].description), + onPress, + }; + }); + return ( <> - {advancedFilters.map((item) => { - const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); - if (item.shouldHide) { - return undefined; - } + {filters.map((filter) => { return ( ); })} From 16380510348b46baffde71addd5accb78aad1389 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 30 Aug 2024 12:45:40 +0200 Subject: [PATCH 13/14] fix function name --- src/pages/Search/AdvancedSearchFilters.tsx | 55 ++++++++++++---------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 526e65154f38..f6314d21ba38 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -27,88 +27,87 @@ import type {CardList, PersonalDetailsList} from '@src/types/onyx'; const baseFilterConfig = { date: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.date' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, }, currency: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.currency' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, }, merchant: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.merchant' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, }, description: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.description' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, }, reportID: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.reportID' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, }, amount: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.total' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, }, category: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.category' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, }, keyword: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'search.filters.hasKeywords' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, }, cardID: { - title: getFilterCardDisplayTitle, + getTitle: getFilterCardDisplayTitle, description: 'common.card' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, - shouldHide: (cards: CardList) => Object.keys(cards).length === 0, }, taxRate: { - title: getFilterTaxRateDisplayTitle, + getTitle: getFilterTaxRateDisplayTitle, description: 'workspace.taxes.taxRate' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, }, expenseType: { - title: getExpenseTypeDisplayTitle, + getTitle: getExpenseTypeDisplayTitle, description: 'search.expenseType' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, }, tag: { - title: getFilterDisplayTitle, + getTitle: getFilterDisplayTitle, description: 'common.tag' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, }, from: { - title: getFilterParticipantDisplayTitle, + getTitle: getFilterParticipantDisplayTitle, description: 'common.from' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, }, to: { - title: getFilterParticipantDisplayTitle, + getTitle: getFilterParticipantDisplayTitle, description: 'common.to' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, }, has: { - title: getFilterHasDisplayTitle, + getTitle: getFilterHasDisplayTitle, description: 'search.filters.has' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, }, }; const typeFiltersKeys: Record>> = { - [CONST.SEARCH.DATA_TYPES.EXPENSE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'expenseType', 'tag', 'from', 'to'], - [CONST.SEARCH.DATA_TYPES.INVOICE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to'], - [CONST.SEARCH.DATA_TYPES.TRIP]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to'], - [CONST.SEARCH.DATA_TYPES.CHAT]: ['date', 'keyword', 'from', 'to', 'has'], + [CONST.SEARCH.DATA_TYPES.EXPENSE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'expenseType', 'tag', 'from', 'to', 'cardID'], + [CONST.SEARCH.DATA_TYPES.INVOICE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to', 'cardID'], + [CONST.SEARCH.DATA_TYPES.TRIP]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to', 'cardID'], + [CONST.SEARCH.DATA_TYPES.CHAT]: ['date', 'keyword', 'from', 'has'], }; function getFilterCardDisplayTitle(filters: Partial, cards: CardList) { @@ -260,15 +259,18 @@ function AdvancedSearchFilters() { key === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG ) { - filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, key, translate); + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID) { - filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, cardList); + if (Object.keys(cardList).length === 0) { + return undefined; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, cardList); } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE) { - filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, taxRates); + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, taxRates); } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS) { - filterTitle = baseFilterConfig[key].title(searchAdvancedFilters, translate); + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, translate); } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO) { - filterTitle = baseFilterConfig[key].title(searchAdvancedFilters[key] ?? [], personalDetails); + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters[key] ?? [], personalDetails); } return { key, @@ -283,6 +285,9 @@ function AdvancedSearchFilters() { {filters.map((filter) => { + if (filter === undefined) { + return undefined; + } return ( Date: Fri, 30 Aug 2024 13:09:19 +0200 Subject: [PATCH 14/14] fix grammar nad parser --- src/libs/SearchParser/searchParser.js | 346 +++++++++-------------- src/libs/SearchParser/searchParser.peggy | 76 ++--- 2 files changed, 167 insertions(+), 255 deletions(-) diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 3699a86e33c3..a93e3fae8551 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -3,11 +3,6 @@ // https://peggyjs.org/ - - function buildFilter(operator, left, right) { - return { operator, left, right }; - } - function peg$subclass(child, parent) { function C() { this.constructor = child; } C.prototype = parent.prototype; @@ -205,7 +200,8 @@ function peg$parse(input, options) { var peg$c22 = "sortBy"; var peg$c23 = "sortOrder"; var peg$c24 = "policyID"; - var peg$c25 = "\""; + var peg$c25 = "has"; + var peg$c26 = "\""; var peg$r0 = /^[:=]/; var peg$r1 = /^[^"\r\n]/; @@ -238,11 +234,12 @@ function peg$parse(input, options) { var peg$e23 = peg$literalExpectation("sortBy", false); var peg$e24 = peg$literalExpectation("sortOrder", false); var peg$e25 = peg$literalExpectation("policyID", false); - var peg$e26 = peg$literalExpectation("\"", false); - var peg$e27 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e28 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); - var peg$e29 = peg$otherExpectation("whitespace"); - var peg$e30 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e26 = peg$literalExpectation("has", false); + var peg$e27 = peg$literalExpectation("\"", false); + var peg$e28 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e29 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); + var peg$e30 = peg$otherExpectation("whitespace"); + var peg$e31 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { const withDefaults = applyDefaults(filters); @@ -253,11 +250,9 @@ function peg$parse(input, options) { return withDefaults; }; var peg$f1 = function(head, tail) { - const allFilters = [head, ...tail.map(([_, filter]) => filter)] - .filter((filter) => filter !== null) - .filter(Boolean); + const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null); if (!allFilters.length) { - return null; + return null; } const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") @@ -318,10 +313,11 @@ function peg$parse(input, options) { var peg$f26 = function() { return "sortBy"; }; var peg$f27 = function() { return "sortOrder"; }; var peg$f28 = function() { return "policyID"; }; - var peg$f29 = function(parts) { return parts.join(''); }; - var peg$f30 = function(chars) { return chars.join(''); }; + var peg$f29 = function() { return "has"; }; + var peg$f30 = function(parts) { return parts.join(''); }; var peg$f31 = function(chars) { return chars.join(''); }; - var peg$f32 = function() { return "and"; }; + var peg$f32 = function(chars) { return chars.join(''); }; + var peg$f33 = function() { return "and"; }; var peg$currPos = options.peg$currPos | 0; var peg$savedPos = peg$currPos; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -541,132 +537,24 @@ function peg$parse(input, options) { } function peg$parsefilter() { - var s0, s1; - - s0 = peg$currPos; - s1 = peg$parsestandardFilter(); - if (s1 === peg$FAILED) { - s1 = peg$parsedefaultFilter(); - if (s1 === peg$FAILED) { - s1 = peg$parsemultiSelectFilter(); - if (s1 === peg$FAILED) { - s1 = peg$parsefreeTextFilter(); - } - } - } - if (s1 !== peg$FAILED) { - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parsedefaultFilter() { var s0, s1, s2, s3, s4, s5, s6; s0 = peg$currPos; s1 = peg$parse_(); - s2 = peg$parsedefaultKey(); - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - s4 = peg$parseoperator(); - if (s4 !== peg$FAILED) { - s5 = peg$parse_(); - s6 = peg$parseidentifier(); - if (s6 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f2(s2, s4, s6); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parsefreeTextFilter() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - s1 = peg$parse_(); - s2 = peg$parseidentifier(); - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - peg$savedPos = s0; - s0 = peg$f3(s2); - } else { - peg$currPos = s0; - s0 = peg$FAILED; + s2 = peg$parsekey(); + if (s2 === peg$FAILED) { + s2 = null; } - - return s0; - } - - function peg$parsemultiSelectFilter() { - var s0, s1, s2, s3, s4, s5, s6; - - s0 = peg$currPos; - s1 = peg$parse_(); - s2 = peg$parsemultiselectKey(); - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - s4 = peg$parseoperator(); - if (s4 !== peg$FAILED) { - s5 = peg$parse_(); - s6 = peg$parseidentifier(); - if (s6 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f4(s2, s4, s6); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; + s3 = peg$parse_(); + s4 = peg$parseoperator(); + if (s4 === peg$FAILED) { + s4 = null; } - - return s0; - } - - function peg$parsestandardFilter() { - var s0, s1, s2, s3, s4, s5, s6; - - s0 = peg$currPos; - s1 = peg$parse_(); - s2 = peg$parsekey(); - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - s4 = peg$parseoperator(); - if (s4 !== peg$FAILED) { - s5 = peg$parse_(); - s6 = peg$parseidentifier(); - if (s6 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f5(s2, s4, s6); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + s5 = peg$parse_(); + s6 = peg$parseidentifier(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s2, s4, s6); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -688,7 +576,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f6(); + s1 = peg$f3(); } s0 = s1; if (s0 === peg$FAILED) { @@ -702,63 +590,63 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f7(); + s1 = peg$f4(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c1) { + if (input.charCodeAt(peg$currPos) === 62) { s1 = peg$c1; - peg$currPos += 2; + peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e2); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f8(); + s1 = peg$f5(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 62) { + if (input.substr(peg$currPos, 2) === peg$c2) { s1 = peg$c2; - peg$currPos++; + peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e3); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f9(); + s1 = peg$f6(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c3) { + if (input.charCodeAt(peg$currPos) === 60) { s1 = peg$c3; - peg$currPos += 2; + peg$currPos++; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e4); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f10(); + s1 = peg$f7(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 60) { + if (input.substr(peg$currPos, 2) === peg$c4) { s1 = peg$c4; - peg$currPos++; + peg$currPos += 2; } else { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e5); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f11(); + s1 = peg$f8(); } s0 = s1; } @@ -773,62 +661,97 @@ function peg$parse(input, options) { function peg$parsekey() { var s0, s1; - peg$silentFails++; s0 = peg$currPos; if (input.substr(peg$currPos, 4) === peg$c5) { s1 = peg$c5; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e7); } + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f9(); } - if (s1 === peg$FAILED) { + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; if (input.substr(peg$currPos, 6) === peg$c6) { s1 = peg$c6; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e8); } + if (peg$silentFails === 0) { peg$fail(peg$e7); } } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 11) === peg$c7) { + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f10(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c7) { s1 = peg$c7; - peg$currPos += 11; + peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e9); } + if (peg$silentFails === 0) { peg$fail(peg$e8); } } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c8) { + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f11(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c8) { s1 = peg$c8; - peg$currPos += 8; + peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e10); } + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f12(); } - if (s1 === peg$FAILED) { + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; if (input.substr(peg$currPos, 11) === peg$c9) { s1 = peg$c9; peg$currPos += 11; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e11); } + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f13(); } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c10) { + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c10) { s1 = peg$c10; - peg$currPos += 8; + peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e12); } + if (peg$silentFails === 0) { peg$fail(peg$e11); } } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 7) === peg$c11) { + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f14(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 8) === peg$c11) { s1 = peg$c11; - peg$currPos += 7; + peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e13); } + if (peg$silentFails === 0) { peg$fail(peg$e12); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1017,6 +940,21 @@ function peg$parse(input, options) { s1 = peg$f28(); } s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 3) === peg$c25) { + s1 = peg$c25; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f29(); + } + s0 = s1; + } } } } @@ -1062,7 +1000,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f29(s1); + s1 = peg$f30(s1); } s0 = s1; @@ -1072,14 +1010,13 @@ function peg$parse(input, options) { function peg$parsequotedString() { var s0, s1, s2, s3; - peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c25; + s1 = peg$c26; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1088,7 +1025,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1097,19 +1034,19 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c25; + s3 = peg$c26; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f30(s2); + s0 = peg$f31(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1118,11 +1055,6 @@ function peg$parse(input, options) { peg$currPos = s0; s0 = peg$FAILED; } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } - } return s0; } @@ -1130,7 +1062,6 @@ function peg$parse(input, options) { function peg$parsealphanumeric() { var s0, s1, s2; - peg$silentFails++; s0 = peg$currPos; s1 = []; s2 = input.charAt(peg$currPos); @@ -1138,7 +1069,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1148,7 +1079,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } } } else { @@ -1156,14 +1087,9 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f31(s1); + s1 = peg$f32(s1); } s0 = s1; - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } - } return s0; } @@ -1171,15 +1097,11 @@ function peg$parse(input, options) { function peg$parselogicalAnd() { var s0, s1; - peg$silentFails++; s0 = peg$currPos; s1 = peg$parse_(); peg$savedPos = s0; - s1 = peg$f32(); + s1 = peg$f33(); s0 = s1; - peg$silentFails--; - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } return s0; } @@ -1194,7 +1116,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1203,28 +1125,32 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } return s0; } const defaultValues = { - type: "expense", - status: "all", - sortBy: "date", - sortOrder: "desc", + "type": "expense", + "status": "all", + "sortBy": "date", + "sortOrder": "desc", }; + function buildFilter(operator, left, right) { + return { operator, left, right }; + } + function applyDefaults(filters) { return { ...defaultValues, - filters, + filters }; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index cfe60932713b..105a8a62bc39 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -5,41 +5,31 @@ // // query: entry point for the parser and rule to process the values returned by the filterList rule. Takes filters as an argument and returns the final AST output. // filterList: rule to process the array of filters returned by the filter rule. It takes head and tail as arguments, filters it for null values and builds the AST. -// filter: abstract rule to simplify the filterList rule. It takes all filter types. -// defaultFilter: rule to process the default values returned by the defaultKey rule. It updates the default values object. -// freeTextFilter: rule to process the free text search values returned by the identifier rule. It builds filter Object. -// multiSelectFilter: rule to process the multi-select values returned by the multiselectKey rule. It builds multiselect filter Object. -// standardFilter: rule to process the values returned by the key rule. It builds filter Object. +// filter: rule to build the filter object. It takes field, operator and value as input and returns {operator, left: field, right: value} or null if the left value is a defaultValues // operator: rule to match pre-defined search syntax operators, e.g. !=, >, etc -// key: rule to match pre-defined search syntax fields that aren't multi-select, e.g. amount, merchant, etc -// defaultKey: rule to match pre-defined search syntax fields that are used to update default values, e.g. type, status, etc -// multiselectKey: rule to match pre-defined search syntax fields that are used for multi-select filters, e.g. in, currency, etc +// key: rule to match pre-defined search syntax fields, e.g. amount, merchant, etc // identifier: composite rule to match patterns defined by the quotedString and alphanumeric rules // quotedString: rule to match a quoted string pattern, e.g. "this is a quoted string" // alphanumeric: rule to match unquoted alphanumeric characters, e.g. a-z, 0-9, _, @, etc // logicalAnd: rule to match whitespace and return it as a logical 'and' operator // whitespace: rule to match whitespaces -// global initializer (code executed only once) -{{ - function buildFilter(operator, left, right) { - return { operator, left, right }; - } -}} - -// per-parser initializer (code executed before every parse) { const defaultValues = { - type: "expense", - status: "all", - sortBy: "date", - sortOrder: "desc", + "type": "expense", + "status": "all", + "sortBy": "date", + "sortOrder": "desc", }; + function buildFilter(operator, left, right) { + return { operator, left, right }; + } + function applyDefaults(filters) { return { ...defaultValues, - filters, + filters }; } @@ -75,11 +65,9 @@ query filterList = head:filter tail:(logicalAnd filter)* { - const allFilters = [head, ...tail.map(([_, filter]) => filter)] - .filter((filter) => filter !== null) - .filter(Boolean); + const allFilters = [head, ...tail.map(([_, filter]) => filter)].filter(filter => filter !== null); if (!allFilters.length) { - return null; + return null; } const keywords = allFilters.filter((filter) => filter.left === "keyword" || filter.right?.left === "keyword") const nonKeywords = allFilters.filter((filter) => filter.left !== "keyword" && filter.right?.left !== "keyword") @@ -107,28 +95,22 @@ filter return null; } -multiSelectFilter - = _ field:multiselectKey _ op:operator _ value:identifier { - return buildFilter(op, field, value); - } + if (!field && !op) { + return buildFilter('eq', 'keyword', value.trim()); + } -standardFilter - = _ field:key _ op:operator _ value:identifier { - const values = value.split(","); - return values - .slice(1) - .reduce( - (acc, val) => - buildFilter("or", acc, buildFilter(op, field, val.trim())), - buildFilter(op, field, values[0]) - ); + const values = value.split(','); + const operatorValue = op ?? 'eq'; + + return values.slice(1).reduce((acc, val) => buildFilter('or', acc, buildFilter(operatorValue, field, val.trim())), buildFilter(operatorValue, field, values[0])); } operator = (":" / "=") { return "eq"; } / "!=" { return "neq"; } - / ">=" { return "gte"; } / ">" { return "gt"; } + / ">=" { return "gte"; } + / "<" { return "lt"; } / "<=" { return "lte"; } key @@ -152,15 +134,19 @@ key / "sortBy" { return "sortBy"; } / "sortOrder" { return "sortOrder"; } / "policyID" { return "policyID"; } + / "has" { return "has"; } identifier = parts:(quotedString / alphanumeric)+ { return parts.join(''); } -quotedString "quote" = "\"" chars:[^"\r\n]* "\"" { return chars.join(""); } +quotedString + = '"' chars:[^"\r\n]* '"' { return chars.join(''); } -alphanumeric "word" - = chars:[A-Za-z0-9_@./#&+\-\\',;]+ { return chars.join(""); } +alphanumeric + = chars:[A-Za-z0-9_@./#&+\-\\',;]+ { return chars.join(''); } -logicalAnd "and" = _ { return "and"; } +logicalAnd + = _ { return "and"; } -_ "whitespace" = [ \t\r\n]* +_ "whitespace" + = [ \t\r\n]*