diff options
author | Thiago Fernando <thiagofernando@outlook.com> | 2019-05-10 14:54:25 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-10 14:54:25 -0300 |
commit | ce0473480bab3fc621d4baecfff0f413e21b5e2c (patch) | |
tree | 06d724b4f80885bdd13137f7977d7a914cca0138 /client/lib/filter.js | |
parent | c43508cacbd64357409a3de114db9dab2ae59a9d (diff) | |
parent | 7ff4067e88ed59686c86d81447fa2ce550032034 (diff) | |
download | wekan-ce0473480bab3fc621d4baecfff0f413e21b5e2c.tar.gz wekan-ce0473480bab3fc621d4baecfff0f413e21b5e2c.tar.bz2 wekan-ce0473480bab3fc621d4baecfff0f413e21b5e2c.zip |
Merge pull request #1 from wekan/devel
ldap changes
Diffstat (limited to 'client/lib/filter.js')
-rw-r--r-- | client/lib/filter.js | 425 |
1 files changed, 412 insertions, 13 deletions
diff --git a/client/lib/filter.js b/client/lib/filter.js index 8129776b..c3c1b070 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -3,17 +3,19 @@ // RangeFilter, dateFilter, etc.). We then define a global `Filter` object whose // goal is to filter complete documents by using the local filters for each // fields. - function showFilterSidebar() { Sidebar.setView('filter'); } // Use a "set" filter for a field that is a set of documents uniquely // identified. For instance `{ labels: ['labelA', 'labelC', 'labelD'] }`. +// use "subField" for searching inside object Fields. +// For instance '{ 'customFields._id': ['field1','field2']} (subField would be: _id) class SetFilter { - constructor() { + constructor(subField = '') { this._dep = new Tracker.Dependency(); this._selectedElements = []; + this.subField = subField; } isSelected(val) { @@ -61,7 +63,9 @@ class SetFilter { _getMongoSelector() { this._dep.depend(); - return { $in: this._selectedElements }; + return { + $in: this._selectedElements, + }; } _getEmptySelector() { @@ -72,10 +76,385 @@ class SetFilter { includeEmpty = true; } }); - return includeEmpty ? { $eq: [] } : null; + return includeEmpty ? { + $eq: [], + } : null; } } + +// 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 regex = 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; + current += char; + continue; + } + if (char === '/') { + string = !string; + if (string) regex = true; + current += char; + continue; + } + if (char === '\'') { + string = !string; + if (string) wasString = true; + continue; + } + if (char === '\\' && !string) { + ignore = true; + continue; + } + if (char === ' ' && !string) { + commands.push({ + 'cmd': current, + 'string': wasString, + regex, + }); + wasString = false; + current = ''; + continue; + } + current += char; + } + if (current !== '') { + commands.push({ + 'cmd': current, + 'string': wasString, + regex, + }); + } + return commands; + } + + _fieldNameToId(field) { + const found = CustomFields.findOne({ + 'name': field, + }); + return found._id; + } + + _fieldValueToId(field, value) { + const found = CustomFields.findOne({ + 'name': field, + }); + if (found.settings.dropdownItems && found.settings.dropdownItems.length > 0) { + for (let i = 0; i < found.settings.dropdownItems.length; i++) { + if (found.settings.dropdownItems[i].name === value) { + return found.settings.dropdownItems[i]._id; + } + } + } + return value; + } + + _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; + if (commands[i + 1].regex) { + const match = str.match(new RegExp('^/(.*?)/([gimy]*)$')); + let regex = null; + if (match.length > 2) + regex = new RegExp(match[1], match[2]); + else + regex = new RegExp(match[1]); + commands[i] = { + 'customFields._id': this._fieldNameToId(field), + 'customFields.value': regex, + }; + } else { + commands[i] = { + 'customFields._id': this._fieldNameToId(field), + 'customFields.value': { + $in: [this._fieldValueToId(field, str), parseInt(str, 10)], + }, + }; + } + 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; + if (commands[i + 1].regex) { + const match = str.match(new RegExp('^/(.*?)/([gimy]*)$')); + let regex = null; + if (match.length > 2) + regex = new RegExp(match[1], match[2]); + else + regex = new RegExp(match[1]); + commands[i] = { + 'customFields._id': this._fieldNameToId(field), + 'customFields.value': { + $not: regex, + }, + }; + } else { + commands[i] = { + 'customFields._id': this._fieldNameToId(field), + 'customFields.value': { + $not: { + $in: [this._fieldValueToId(field, str), parseInt(str, 10)], + }, + }, + }; + } + 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: parseInt(str, 10), + }, + }; + 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: parseInt(str, 10), + }, + }; + 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: parseInt(str, 10), + }, + }; + 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: parseInt(str, 10), + }, + }; + 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 @@ -86,8 +465,10 @@ Filter = { // before changing the schema. labelIds: new SetFilter(), members: new SetFilter(), + customFields: new SetFilter('_id'), + advanced: new AdvancedFilter(), - _fields: ['labelIds', 'members'], + _fields: ['labelIds', 'members', 'customFields'], // We don't filter cards that have been added after the last filter change. To // implement this we keep the id of these cards in this `_exceptions` fields @@ -98,7 +479,7 @@ Filter = { isActive() { return _.any(this._fields, (fieldName) => { return this[fieldName]._isActive(); - }); + }) || this.advanced._isActive(); }, _getMongoSelector() { @@ -111,7 +492,11 @@ Filter = { this._fields.forEach((fieldName) => { const filter = this[fieldName]; if (filter._isActive()) { - filterSelector[fieldName] = filter._getMongoSelector(); + if (filter.subField !== '') { + filterSelector[`${fieldName}.${filter.subField}`] = filter._getMongoSelector(); + } else { + filterSelector[fieldName] = filter._getMongoSelector(); + } emptySelector[fieldName] = filter._getEmptySelector(); if (emptySelector[fieldName] !== null) { includeEmptySelectors = true; @@ -119,13 +504,24 @@ Filter = { } }); - const exceptionsSelector = {_id: {$in: this._exceptions}}; + 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) { @@ -133,7 +529,9 @@ Filter = { if (_.isUndefined(additionalSelector)) return filterSelector; else - return {$and: [filterSelector, additionalSelector]}; + return { + $and: [filterSelector, additionalSelector], + }; }, reset() { @@ -141,6 +539,7 @@ Filter = { const filter = this[fieldName]; filter.reset(); }); + this.advanced.reset(); this.resetExceptions(); }, |