diff options
author | Lauri Ojansivu <x@xet7.org> | 2018-05-20 22:53:36 +0300 |
---|---|---|
committer | Lauri Ojansivu <x@xet7.org> | 2018-05-20 22:53:36 +0300 |
commit | e8206f298325093d02280780006ccf87724afba5 (patch) | |
tree | 697f9dcca12cc240f45dc28fa5dbcce2f4fb36e5 /client/lib | |
parent | 1133b7a04a99f4068298d7bc2c1f3428b1bba539 (diff) | |
parent | 2119a6568b36591f84df29e0305fda9c12ec207a (diff) | |
download | wekan-e8206f298325093d02280780006ccf87724afba5.tar.gz wekan-e8206f298325093d02280780006ccf87724afba5.tar.bz2 wekan-e8206f298325093d02280780006ccf87724afba5.zip |
Merge branch 'feature-advanced-filter' of https://github.com/feuerball11/wekan into feuerball11-feature-advanced-filter
Diffstat (limited to 'client/lib')
-rw-r--r-- | client/lib/filter.js | 313 |
1 files changed, 308 insertions, 5 deletions
diff --git a/client/lib/filter.js b/client/lib/filter.js index f68c9711..c5f8fe7e 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -79,6 +79,302 @@ class SetFilter { } } + +// Advanced filter forms a MongoSelector from a users String. +// Build by: Ignatz 19.05.2018 (github feuerball11) +class AdvancedFilter { + constructor() { + this._dep = new Tracker.Dependency(); + this._filter = ''; + this._lastValide={}; + } + + set(str) + { + this._filter = str; + this._dep.changed(); + } + + reset() { + this._filter = ''; + this._lastValide={}; + this._dep.changed(); + } + + _isActive() { + this._dep.depend(); + return this._filter !== ''; + } + + _filterToCommands(){ + const commands = []; + let current = ''; + let string = false; + let wasString = false; + let ignore = false; + for (let i = 0; i < this._filter.length; i++) + { + const char = this._filter.charAt(i); + if (ignore) + { + ignore = false; + continue; + } + if (char === '\'') + { + string = !string; + if (string) wasString = true; + continue; + } + if (char === '\\') + { + ignore = true; + continue; + } + if (char === ' ' && !string) + { + commands.push({'cmd':current, 'string':wasString}); + wasString = false; + current = ''; + continue; + } + current += char; + } + if (current !== '') + { + commands.push({'cmd':current, 'string':wasString}); + } + return commands; + } + + _fieldNameToId(field) + { + const found = CustomFields.findOne({'name':field}); + return found._id; + } + + _arrayToSelector(commands) + { + try { + //let changed = false; + this._processSubCommands(commands); + } + catch (e){return this._lastValide;} + this._lastValide = {$or: commands}; + return {$or: commands}; + } + + _processSubCommands(commands) + { + const subcommands = []; + let level = 0; + let start = -1; + for (let i = 0; i < commands.length; i++) + { + if (commands[i].cmd) + { + switch (commands[i].cmd) + { + case '(': + { + level++; + if (start === -1) start = i; + continue; + } + case ')': + { + level--; + commands.splice(i, 1); + i--; + continue; + } + default: + { + if (level > 0) + { + subcommands.push(commands[i]); + commands.splice(i, 1); + i--; + continue; + } + } + } + } + } + if (start !== -1) + { + this._processSubCommands(subcommands); + if (subcommands.length === 1) + commands.splice(start, 0, subcommands[0]); + else + commands.splice(start, 0, subcommands); + } + this._processConditions(commands); + this._processLogicalOperators(commands); + } + + _processConditions(commands) + { + for (let i = 0; i < commands.length; i++) + { + if (!commands[i].string && commands[i].cmd) + { + switch (commands[i].cmd) + { + case '=': + case '==': + case '===': + { + const field = commands[i-1].cmd; + const str = commands[i+1].cmd; + commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value':str}; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + case '!=': + case '!==': + { + const field = commands[i-1].cmd; + const str = commands[i+1].cmd; + commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $not: str }}; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + case '>': + case 'gt': + case 'Gt': + case 'GT': + { + const field = commands[i-1].cmd; + const str = commands[i+1].cmd; + commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $gt: str } }; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + case '>=': + case '>==': + case 'gte': + case 'Gte': + case 'GTE': + { + const field = commands[i-1].cmd; + const str = commands[i+1].cmd; + commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $gte: str } }; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + case '<': + case 'lt': + case 'Lt': + case 'LT': + { + const field = commands[i-1].cmd; + const str = commands[i+1].cmd; + commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $lt: str } }; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + case '<=': + case '<==': + case 'lte': + case 'Lte': + case 'LTE': + { + const field = commands[i-1].cmd; + const str = commands[i+1].cmd; + commands[i] = {'customFields._id':this._fieldNameToId(field), 'customFields.value': { $lte: str } }; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + + } + } + } + } + + _processLogicalOperators(commands) + { + for (let i = 0; i < commands.length; i++) + { + if (!commands[i].string && commands[i].cmd) + { + switch (commands[i].cmd) + { + case 'or': + case 'Or': + case 'OR': + case '|': + case '||': + { + const op1 = commands[i-1]; + const op2 = commands[i+1]; + commands[i] = {$or: [op1, op2]}; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + case 'and': + case 'And': + case 'AND': + case '&': + case '&&': + { + const op1 = commands[i-1]; + const op2 = commands[i+1]; + commands[i] = {$and: [op1, op2]}; + commands.splice(i-1, 1); + commands.splice(i, 1); + //changed = true; + i--; + break; + } + + case 'not': + case 'Not': + case 'NOT': + case '!': + { + const op1 = commands[i+1]; + commands[i] = {$not: op1}; + commands.splice(i+1, 1); + //changed = true; + i--; + break; + } + + } + } + } + } + + _getMongoSelector() { + this._dep.depend(); + const commands = this._filterToCommands(); + return this._arrayToSelector(commands); + } + +} + // The global Filter object. // XXX It would be possible to re-write this object more elegantly, and removing // the need to provide a list of `_fields`. We also should move methods into the @@ -90,6 +386,7 @@ Filter = { labelIds: new SetFilter(), members: new SetFilter(), customFields: new SetFilter('_id'), + advanced: new AdvancedFilter(), _fields: ['labelIds', 'members', 'customFields'], @@ -102,7 +399,7 @@ Filter = { isActive() { return _.any(this._fields, (fieldName) => { return this[fieldName]._isActive(); - }); + }) || this.advanced._isActive(); }, _getMongoSelector() { @@ -133,10 +430,15 @@ Filter = { const exceptionsSelector = {_id: {$in: this._exceptions}}; this._exceptionsDep.depend(); - if (includeEmptySelectors) - return {$or: [filterSelector, exceptionsSelector, emptySelector]}; - else - return {$or: [filterSelector, exceptionsSelector]}; + const selectors = [exceptionsSelector]; + + if (_.any(this._fields, (fieldName) => { + return this[fieldName]._isActive(); + })) selectors.push(filterSelector); + if (includeEmptySelectors) selectors.push(emptySelector); + if (this.advanced._isActive()) selectors.push(this.advanced._getMongoSelector()); + + return {$or: selectors}; }, mongoSelector(additionalSelector) { @@ -152,6 +454,7 @@ Filter = { const filter = this[fieldName]; filter.reset(); }); + this.advanced.reset(); this.resetExceptions(); }, |