mirror of
https://github.com/wekan/wekan.git
synced 2025-12-17 07:50:12 +01:00
Move query parsing to Query class
This commit is contained in:
parent
097cae1f8c
commit
6def7d6f70
3 changed files with 374 additions and 381 deletions
|
|
@ -1,48 +1,6 @@
|
||||||
import { CardSearchPagedComponent } from '../../lib/cardSearch';
|
import { CardSearchPagedComponent } from '../../lib/cardSearch';
|
||||||
import Boards from '../../../models/boards';
|
import Boards from '../../../models/boards';
|
||||||
import moment from 'moment';
|
import { Query, QueryErrors } from '../../../config/query-classes';
|
||||||
import {
|
|
||||||
OPERATOR_ASSIGNEE,
|
|
||||||
OPERATOR_BOARD,
|
|
||||||
OPERATOR_COMMENT,
|
|
||||||
OPERATOR_CREATED_AT,
|
|
||||||
OPERATOR_DUE,
|
|
||||||
OPERATOR_HAS,
|
|
||||||
OPERATOR_LABEL,
|
|
||||||
OPERATOR_LIMIT,
|
|
||||||
OPERATOR_LIST,
|
|
||||||
OPERATOR_MEMBER,
|
|
||||||
OPERATOR_MODIFIED_AT,
|
|
||||||
OPERATOR_SORT,
|
|
||||||
OPERATOR_STATUS,
|
|
||||||
OPERATOR_SWIMLANE,
|
|
||||||
OPERATOR_UNKNOWN,
|
|
||||||
OPERATOR_USER,
|
|
||||||
ORDER_ASCENDING,
|
|
||||||
ORDER_DESCENDING,
|
|
||||||
PREDICATE_ALL,
|
|
||||||
PREDICATE_ARCHIVED,
|
|
||||||
PREDICATE_ASSIGNEES,
|
|
||||||
PREDICATE_ATTACHMENT,
|
|
||||||
PREDICATE_CHECKLIST,
|
|
||||||
PREDICATE_CREATED_AT,
|
|
||||||
PREDICATE_DESCRIPTION,
|
|
||||||
PREDICATE_DUE_AT,
|
|
||||||
PREDICATE_END_AT,
|
|
||||||
PREDICATE_ENDED,
|
|
||||||
PREDICATE_MEMBERS,
|
|
||||||
PREDICATE_MODIFIED_AT,
|
|
||||||
PREDICATE_MONTH,
|
|
||||||
PREDICATE_OPEN,
|
|
||||||
PREDICATE_OVERDUE,
|
|
||||||
PREDICATE_PRIVATE,
|
|
||||||
PREDICATE_PUBLIC,
|
|
||||||
PREDICATE_QUARTER,
|
|
||||||
PREDICATE_START_AT,
|
|
||||||
PREDICATE_WEEK,
|
|
||||||
PREDICATE_YEAR,
|
|
||||||
} from '../../../config/search-const';
|
|
||||||
import { QueryErrors, QueryParams } from '../../../config/query-classes';
|
|
||||||
|
|
||||||
// const subManager = new SubsManager();
|
// const subManager = new SubsManager();
|
||||||
|
|
||||||
|
|
@ -62,24 +20,6 @@ Template.globalSearch.helpers({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-due-cards-view-me'() {
|
|
||||||
Utils.setDueCardsView('me');
|
|
||||||
Popup.close();
|
|
||||||
},
|
|
||||||
|
|
||||||
'click .js-due-cards-view-all'() {
|
|
||||||
Utils.setDueCardsView('all');
|
|
||||||
Popup.close();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
}).register('globalSearchViewChangePopup');
|
|
||||||
|
|
||||||
class GlobalSearchComponent extends CardSearchPagedComponent {
|
class GlobalSearchComponent extends CardSearchPagedComponent {
|
||||||
onCreated() {
|
onCreated() {
|
||||||
super.onCreated();
|
super.onCreated();
|
||||||
|
|
@ -87,7 +27,6 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
|
||||||
this.myLabelNames = new ReactiveVar([]);
|
this.myLabelNames = new ReactiveVar([]);
|
||||||
this.myBoardNames = new ReactiveVar([]);
|
this.myBoardNames = new ReactiveVar([]);
|
||||||
this.parsingErrors = new QueryErrors();
|
this.parsingErrors = new QueryErrors();
|
||||||
this.colorMap = null;
|
|
||||||
this.queryParams = null;
|
this.queryParams = null;
|
||||||
|
|
||||||
Meteor.call('myLists', (err, data) => {
|
Meteor.call('myLists', (err, data) => {
|
||||||
|
|
@ -114,9 +53,6 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
//console.log('lang:', TAPi18n.getLanguage());
|
//console.log('lang:', TAPi18n.getLanguage());
|
||||||
this.colorMap = Boards.colorMap();
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
// console.log('colorMap:', this.colorMap);
|
|
||||||
|
|
||||||
if (Session.get('globalQuery')) {
|
if (Session.get('globalQuery')) {
|
||||||
this.searchAllBoards(Session.get('globalQuery'));
|
this.searchAllBoards(Session.get('globalQuery'));
|
||||||
|
|
@ -139,327 +75,38 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
|
||||||
this.parsingErrors.errorMessages();
|
this.parsingErrors.errorMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchAllBoards(query) {
|
searchAllBoards(queryText) {
|
||||||
query = query.trim();
|
queryText = queryText.trim();
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
//console.log('query:', query);
|
//console.log('queryText:', queryText);
|
||||||
|
|
||||||
this.query.set(query);
|
this.query.set(queryText);
|
||||||
|
|
||||||
this.resetSearch();
|
this.resetSearch();
|
||||||
|
|
||||||
if (!query) {
|
if (!queryText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.searching.set(true);
|
this.searching.set(true);
|
||||||
|
|
||||||
const reOperator1 = new RegExp(
|
const query = new Query();
|
||||||
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)',
|
query.buildParams(queryText);
|
||||||
'iu',
|
|
||||||
);
|
|
||||||
const reOperator2 = new RegExp(
|
|
||||||
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)',
|
|
||||||
'iu',
|
|
||||||
);
|
|
||||||
const reText = new RegExp('^(?<text>\\S+)(\\s+|$)', 'u');
|
|
||||||
const reQuotedText = new RegExp(
|
|
||||||
'^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
|
|
||||||
'u',
|
|
||||||
);
|
|
||||||
const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
|
|
||||||
|
|
||||||
const operators = {
|
|
||||||
'operator-board': OPERATOR_BOARD,
|
|
||||||
'operator-board-abbrev': OPERATOR_BOARD,
|
|
||||||
'operator-swimlane': OPERATOR_SWIMLANE,
|
|
||||||
'operator-swimlane-abbrev': OPERATOR_SWIMLANE,
|
|
||||||
'operator-list': OPERATOR_LIST,
|
|
||||||
'operator-list-abbrev': OPERATOR_LIST,
|
|
||||||
'operator-label': OPERATOR_LABEL,
|
|
||||||
'operator-label-abbrev': OPERATOR_LABEL,
|
|
||||||
'operator-user': OPERATOR_USER,
|
|
||||||
'operator-user-abbrev': OPERATOR_USER,
|
|
||||||
'operator-member': OPERATOR_MEMBER,
|
|
||||||
'operator-member-abbrev': OPERATOR_MEMBER,
|
|
||||||
'operator-assignee': OPERATOR_ASSIGNEE,
|
|
||||||
'operator-assignee-abbrev': OPERATOR_ASSIGNEE,
|
|
||||||
'operator-status': OPERATOR_STATUS,
|
|
||||||
'operator-due': OPERATOR_DUE,
|
|
||||||
'operator-created': OPERATOR_CREATED_AT,
|
|
||||||
'operator-modified': OPERATOR_MODIFIED_AT,
|
|
||||||
'operator-comment': OPERATOR_COMMENT,
|
|
||||||
'operator-has': OPERATOR_HAS,
|
|
||||||
'operator-sort': OPERATOR_SORT,
|
|
||||||
'operator-limit': OPERATOR_LIMIT,
|
|
||||||
};
|
|
||||||
|
|
||||||
const predicates = {
|
|
||||||
due: {
|
|
||||||
'predicate-overdue': PREDICATE_OVERDUE,
|
|
||||||
},
|
|
||||||
durations: {
|
|
||||||
'predicate-week': PREDICATE_WEEK,
|
|
||||||
'predicate-month': PREDICATE_MONTH,
|
|
||||||
'predicate-quarter': PREDICATE_QUARTER,
|
|
||||||
'predicate-year': PREDICATE_YEAR,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
'predicate-archived': PREDICATE_ARCHIVED,
|
|
||||||
'predicate-all': PREDICATE_ALL,
|
|
||||||
'predicate-open': PREDICATE_OPEN,
|
|
||||||
'predicate-ended': PREDICATE_ENDED,
|
|
||||||
'predicate-public': PREDICATE_PUBLIC,
|
|
||||||
'predicate-private': PREDICATE_PRIVATE,
|
|
||||||
},
|
|
||||||
sorts: {
|
|
||||||
'predicate-due': PREDICATE_DUE_AT,
|
|
||||||
'predicate-created': PREDICATE_CREATED_AT,
|
|
||||||
'predicate-modified': PREDICATE_MODIFIED_AT,
|
|
||||||
},
|
|
||||||
has: {
|
|
||||||
'predicate-description': PREDICATE_DESCRIPTION,
|
|
||||||
'predicate-checklist': PREDICATE_CHECKLIST,
|
|
||||||
'predicate-attachment': PREDICATE_ATTACHMENT,
|
|
||||||
'predicate-start': PREDICATE_START_AT,
|
|
||||||
'predicate-end': PREDICATE_END_AT,
|
|
||||||
'predicate-due': PREDICATE_DUE_AT,
|
|
||||||
'predicate-assignee': PREDICATE_ASSIGNEES,
|
|
||||||
'predicate-member': PREDICATE_MEMBERS,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const predicateTranslations = {};
|
|
||||||
Object.entries(predicates).forEach(([category, catPreds]) => {
|
|
||||||
predicateTranslations[category] = {};
|
|
||||||
Object.entries(catPreds).forEach(([tag, value]) => {
|
|
||||||
predicateTranslations[category][TAPi18n.__(tag)] = value;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
// console.log('predicateTranslations:', predicateTranslations);
|
|
||||||
|
|
||||||
const operatorMap = {};
|
|
||||||
Object.entries(operators).forEach(([key, value]) => {
|
|
||||||
operatorMap[TAPi18n.__(key).toLowerCase()] = value;
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
// console.log('operatorMap:', operatorMap);
|
|
||||||
|
|
||||||
const params = new QueryParams();
|
|
||||||
let text = '';
|
|
||||||
while (query) {
|
|
||||||
let m = query.match(reOperator1);
|
|
||||||
if (!m) {
|
|
||||||
m = query.match(reOperator2);
|
|
||||||
if (m) {
|
|
||||||
query = query.replace(reOperator2, '');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query = query.replace(reOperator1, '');
|
|
||||||
}
|
|
||||||
if (m) {
|
|
||||||
let op;
|
|
||||||
if (m.groups.operator) {
|
|
||||||
op = m.groups.operator.toLowerCase();
|
|
||||||
} else {
|
|
||||||
op = m.groups.abbrev.toLowerCase();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (operatorMap.hasOwnProperty(op)) {
|
|
||||||
const operator = operatorMap[op];
|
|
||||||
let value = m.groups.value;
|
|
||||||
if (operator === OPERATOR_LABEL) {
|
|
||||||
if (value in this.colorMap) {
|
|
||||||
value = this.colorMap[value];
|
|
||||||
// console.log('found color:', value);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
[OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes(
|
|
||||||
operator,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const days = parseInt(value, 10);
|
|
||||||
let duration = null;
|
|
||||||
if (isNaN(days)) {
|
|
||||||
// duration was specified as text
|
|
||||||
if (predicateTranslations.durations[value]) {
|
|
||||||
duration = predicateTranslations.durations[value];
|
|
||||||
let date = null;
|
|
||||||
switch (duration) {
|
|
||||||
case PREDICATE_WEEK:
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const week = moment().week();
|
|
||||||
if (week === 52) {
|
|
||||||
date = moment(1, 'W');
|
|
||||||
date.set('year', date.year() + 1);
|
|
||||||
} else {
|
|
||||||
date = moment(week + 1, 'W');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PREDICATE_MONTH:
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const month = moment().month();
|
|
||||||
// .month() is zero indexed
|
|
||||||
if (month === 11) {
|
|
||||||
date = moment(1, 'M');
|
|
||||||
date.set('year', date.year() + 1);
|
|
||||||
} else {
|
|
||||||
date = moment(month + 2, 'M');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PREDICATE_QUARTER:
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const quarter = moment().quarter();
|
|
||||||
if (quarter === 4) {
|
|
||||||
date = moment(1, 'Q');
|
|
||||||
date.set('year', date.year() + 1);
|
|
||||||
} else {
|
|
||||||
date = moment(quarter + 1, 'Q');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PREDICATE_YEAR:
|
|
||||||
date = moment(moment().year() + 1, 'YYYY');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (date) {
|
|
||||||
value = {
|
|
||||||
operator: '$lt',
|
|
||||||
value: date.format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
operator === OPERATOR_DUE &&
|
|
||||||
value === PREDICATE_OVERDUE
|
|
||||||
) {
|
|
||||||
value = {
|
|
||||||
operator: '$lt',
|
|
||||||
value: moment().format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.parsingErrors.addError(OPERATOR_DUE, {
|
|
||||||
tag: 'operator-number-expected',
|
|
||||||
value: { operator: op, value },
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (operator === OPERATOR_DUE) {
|
|
||||||
value = {
|
|
||||||
operator: '$lt',
|
|
||||||
value: moment(moment().format('YYYY-MM-DD'))
|
|
||||||
.add(days + 1, duration ? duration : 'days')
|
|
||||||
.format(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
value = {
|
|
||||||
operator: '$gte',
|
|
||||||
value: moment(moment().format('YYYY-MM-DD'))
|
|
||||||
.subtract(days, duration ? duration : 'days')
|
|
||||||
.format(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (operator === OPERATOR_SORT) {
|
|
||||||
let negated = false;
|
|
||||||
const m = value.match(reNegatedOperator);
|
|
||||||
if (m) {
|
|
||||||
value = m.groups.operator;
|
|
||||||
negated = true;
|
|
||||||
}
|
|
||||||
if (!predicateTranslations.sorts[value]) {
|
|
||||||
this.parsingErrors.addError(OPERATOR_SORT, {
|
|
||||||
tag: 'operator-sort-invalid',
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
value = {
|
|
||||||
name: predicateTranslations.sorts[value],
|
|
||||||
order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (operator === OPERATOR_STATUS) {
|
|
||||||
if (!predicateTranslations.status[value]) {
|
|
||||||
this.parsingErrors.addError(OPERATOR_STATUS, {
|
|
||||||
tag: 'operator-status-invalid',
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
value = predicateTranslations.status[value];
|
|
||||||
}
|
|
||||||
} else if (operator === OPERATOR_HAS) {
|
|
||||||
let negated = false;
|
|
||||||
const m = value.match(reNegatedOperator);
|
|
||||||
if (m) {
|
|
||||||
value = m.groups.operator;
|
|
||||||
negated = true;
|
|
||||||
}
|
|
||||||
if (!predicateTranslations.has[value]) {
|
|
||||||
this.parsingErrors.addError(OPERATOR_HAS, {
|
|
||||||
tag: 'operator-has-invalid',
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
value = {
|
|
||||||
field: predicateTranslations.has[value],
|
|
||||||
exists: !negated,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (operator === OPERATOR_LIMIT) {
|
|
||||||
const limit = parseInt(value, 10);
|
|
||||||
if (isNaN(limit) || limit < 1) {
|
|
||||||
this.parsingErrors.addError(OPERATOR_LIMIT, {
|
|
||||||
tag: 'operator-limit-invalid',
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
value = limit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params.addPredicate(operator, value);
|
|
||||||
} else {
|
|
||||||
this.parsingErrors.addError(OPERATOR_UNKNOWN, {
|
|
||||||
tag: 'operator-unknown-error',
|
|
||||||
value: op,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
m = query.match(reQuotedText);
|
|
||||||
if (!m) {
|
|
||||||
m = query.match(reText);
|
|
||||||
if (m) {
|
|
||||||
query = query.replace(reText, '');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query = query.replace(reQuotedText, '');
|
|
||||||
}
|
|
||||||
if (m) {
|
|
||||||
text += (text ? ' ' : '') + m.groups.text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
// console.log('text:', text);
|
// console.log('params:', query.getParams());
|
||||||
params.text = text;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
this.queryParams = query.getParams();
|
||||||
console.log('params:', params);
|
|
||||||
|
|
||||||
this.queryParams = params;
|
if (query.hasErrors()) {
|
||||||
|
|
||||||
if (this.parsingErrors.hasErrors()) {
|
|
||||||
this.searching.set(false);
|
this.searching.set(false);
|
||||||
this.queryErrors = this.parsingErrorMessages();
|
this.queryErrors = query.errors();
|
||||||
this.hasResults.set(true);
|
this.hasResults.set(true);
|
||||||
this.hasQueryErrors.set(true);
|
this.hasQueryErrors.set(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.runGlobalSearch(params.getParams());
|
this.runGlobalSearch(query.getParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
searchInstructions() {
|
searchInstructions() {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,45 @@ import {
|
||||||
OPERATOR_ASSIGNEE,
|
OPERATOR_ASSIGNEE,
|
||||||
OPERATOR_BOARD,
|
OPERATOR_BOARD,
|
||||||
OPERATOR_COMMENT,
|
OPERATOR_COMMENT,
|
||||||
|
OPERATOR_CREATED_AT,
|
||||||
|
OPERATOR_DUE,
|
||||||
|
OPERATOR_HAS,
|
||||||
OPERATOR_LABEL,
|
OPERATOR_LABEL,
|
||||||
|
OPERATOR_LIMIT,
|
||||||
OPERATOR_LIST,
|
OPERATOR_LIST,
|
||||||
OPERATOR_MEMBER,
|
OPERATOR_MEMBER,
|
||||||
|
OPERATOR_MODIFIED_AT,
|
||||||
|
OPERATOR_SORT,
|
||||||
|
OPERATOR_STATUS,
|
||||||
OPERATOR_SWIMLANE,
|
OPERATOR_SWIMLANE,
|
||||||
|
OPERATOR_UNKNOWN,
|
||||||
OPERATOR_USER,
|
OPERATOR_USER,
|
||||||
|
ORDER_ASCENDING,
|
||||||
|
ORDER_DESCENDING,
|
||||||
|
PREDICATE_ALL,
|
||||||
|
PREDICATE_ARCHIVED,
|
||||||
|
PREDICATE_ASSIGNEES,
|
||||||
|
PREDICATE_ATTACHMENT,
|
||||||
|
PREDICATE_CHECKLIST,
|
||||||
|
PREDICATE_CREATED_AT,
|
||||||
|
PREDICATE_DESCRIPTION,
|
||||||
|
PREDICATE_DUE_AT,
|
||||||
|
PREDICATE_END_AT,
|
||||||
|
PREDICATE_ENDED,
|
||||||
|
PREDICATE_MEMBERS,
|
||||||
|
PREDICATE_MODIFIED_AT,
|
||||||
|
PREDICATE_MONTH,
|
||||||
|
PREDICATE_OPEN,
|
||||||
|
PREDICATE_OVERDUE,
|
||||||
|
PREDICATE_PRIVATE,
|
||||||
|
PREDICATE_PUBLIC,
|
||||||
|
PREDICATE_QUARTER,
|
||||||
|
PREDICATE_START_AT,
|
||||||
|
PREDICATE_WEEK,
|
||||||
|
PREDICATE_YEAR,
|
||||||
} from './search-const';
|
} from './search-const';
|
||||||
import Boards from '../models/boards';
|
import Boards from '../models/boards';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export class QueryParams {
|
export class QueryParams {
|
||||||
text = '';
|
text = '';
|
||||||
|
|
@ -106,7 +138,8 @@ export class QueryErrors {
|
||||||
|
|
||||||
errors() {
|
errors() {
|
||||||
const errs = [];
|
const errs = [];
|
||||||
Object.entries(this._errors).forEach(([operator, errors]) => {
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
Object.entries(this._errors).forEach(([, errors]) => {
|
||||||
errors.forEach(err => {
|
errors.forEach(err => {
|
||||||
errs.push(err);
|
errs.push(err);
|
||||||
});
|
});
|
||||||
|
|
@ -116,7 +149,8 @@ export class QueryErrors {
|
||||||
|
|
||||||
errorMessages() {
|
errorMessages() {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
Object.entries(this._errors).forEach(([operator, errors]) => {
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
Object.entries(this._errors).forEach(([, errors]) => {
|
||||||
errors.forEach(err => {
|
errors.forEach(err => {
|
||||||
messages.push(TAPi18n.__(err.tag, err.value));
|
messages.push(TAPi18n.__(err.tag, err.value));
|
||||||
});
|
});
|
||||||
|
|
@ -126,12 +160,14 @@ export class QueryErrors {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Query {
|
export class Query {
|
||||||
params = {};
|
|
||||||
selector = {};
|
selector = {};
|
||||||
projection = {};
|
projection = {};
|
||||||
|
|
||||||
constructor(selector, projection) {
|
constructor(selector, projection) {
|
||||||
this._errors = new QueryErrors();
|
this._errors = new QueryErrors();
|
||||||
|
this.queryParams = new QueryParams();
|
||||||
|
this.colorMap = Boards.colorMap();
|
||||||
|
|
||||||
if (selector) {
|
if (selector) {
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
}
|
}
|
||||||
|
|
@ -152,4 +188,312 @@ export class Query {
|
||||||
errorMessages() {
|
errorMessages() {
|
||||||
return this._errors.errorMessages();
|
return this._errors.errorMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getParams() {
|
||||||
|
return this.queryParams.getParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
buildParams(queryText) {
|
||||||
|
queryText = queryText.trim();
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
//console.log('query:', query);
|
||||||
|
|
||||||
|
if (!queryText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reOperator1 = new RegExp(
|
||||||
|
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)',
|
||||||
|
'iu',
|
||||||
|
);
|
||||||
|
const reOperator2 = new RegExp(
|
||||||
|
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)',
|
||||||
|
'iu',
|
||||||
|
);
|
||||||
|
const reText = new RegExp('^(?<text>\\S+)(\\s+|$)', 'u');
|
||||||
|
const reQuotedText = new RegExp(
|
||||||
|
'^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
|
||||||
|
'u',
|
||||||
|
);
|
||||||
|
const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
|
||||||
|
|
||||||
|
const operators = {
|
||||||
|
'operator-board': OPERATOR_BOARD,
|
||||||
|
'operator-board-abbrev': OPERATOR_BOARD,
|
||||||
|
'operator-swimlane': OPERATOR_SWIMLANE,
|
||||||
|
'operator-swimlane-abbrev': OPERATOR_SWIMLANE,
|
||||||
|
'operator-list': OPERATOR_LIST,
|
||||||
|
'operator-list-abbrev': OPERATOR_LIST,
|
||||||
|
'operator-label': OPERATOR_LABEL,
|
||||||
|
'operator-label-abbrev': OPERATOR_LABEL,
|
||||||
|
'operator-user': OPERATOR_USER,
|
||||||
|
'operator-user-abbrev': OPERATOR_USER,
|
||||||
|
'operator-member': OPERATOR_MEMBER,
|
||||||
|
'operator-member-abbrev': OPERATOR_MEMBER,
|
||||||
|
'operator-assignee': OPERATOR_ASSIGNEE,
|
||||||
|
'operator-assignee-abbrev': OPERATOR_ASSIGNEE,
|
||||||
|
'operator-status': OPERATOR_STATUS,
|
||||||
|
'operator-due': OPERATOR_DUE,
|
||||||
|
'operator-created': OPERATOR_CREATED_AT,
|
||||||
|
'operator-modified': OPERATOR_MODIFIED_AT,
|
||||||
|
'operator-comment': OPERATOR_COMMENT,
|
||||||
|
'operator-has': OPERATOR_HAS,
|
||||||
|
'operator-sort': OPERATOR_SORT,
|
||||||
|
'operator-limit': OPERATOR_LIMIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
const predicates = {
|
||||||
|
due: {
|
||||||
|
'predicate-overdue': PREDICATE_OVERDUE,
|
||||||
|
},
|
||||||
|
durations: {
|
||||||
|
'predicate-week': PREDICATE_WEEK,
|
||||||
|
'predicate-month': PREDICATE_MONTH,
|
||||||
|
'predicate-quarter': PREDICATE_QUARTER,
|
||||||
|
'predicate-year': PREDICATE_YEAR,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
'predicate-archived': PREDICATE_ARCHIVED,
|
||||||
|
'predicate-all': PREDICATE_ALL,
|
||||||
|
'predicate-open': PREDICATE_OPEN,
|
||||||
|
'predicate-ended': PREDICATE_ENDED,
|
||||||
|
'predicate-public': PREDICATE_PUBLIC,
|
||||||
|
'predicate-private': PREDICATE_PRIVATE,
|
||||||
|
},
|
||||||
|
sorts: {
|
||||||
|
'predicate-due': PREDICATE_DUE_AT,
|
||||||
|
'predicate-created': PREDICATE_CREATED_AT,
|
||||||
|
'predicate-modified': PREDICATE_MODIFIED_AT,
|
||||||
|
},
|
||||||
|
has: {
|
||||||
|
'predicate-description': PREDICATE_DESCRIPTION,
|
||||||
|
'predicate-checklist': PREDICATE_CHECKLIST,
|
||||||
|
'predicate-attachment': PREDICATE_ATTACHMENT,
|
||||||
|
'predicate-start': PREDICATE_START_AT,
|
||||||
|
'predicate-end': PREDICATE_END_AT,
|
||||||
|
'predicate-due': PREDICATE_DUE_AT,
|
||||||
|
'predicate-assignee': PREDICATE_ASSIGNEES,
|
||||||
|
'predicate-member': PREDICATE_MEMBERS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const predicateTranslations = {};
|
||||||
|
Object.entries(predicates).forEach(([category, catPreds]) => {
|
||||||
|
predicateTranslations[category] = {};
|
||||||
|
Object.entries(catPreds).forEach(([tag, value]) => {
|
||||||
|
predicateTranslations[category][TAPi18n.__(tag)] = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
// console.log('predicateTranslations:', predicateTranslations);
|
||||||
|
|
||||||
|
const operatorMap = {};
|
||||||
|
Object.entries(operators).forEach(([key, value]) => {
|
||||||
|
operatorMap[TAPi18n.__(key).toLowerCase()] = value;
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
// console.log('operatorMap:', operatorMap);
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
while (queryText) {
|
||||||
|
let m = queryText.match(reOperator1);
|
||||||
|
if (!m) {
|
||||||
|
m = queryText.match(reOperator2);
|
||||||
|
if (m) {
|
||||||
|
queryText = queryText.replace(reOperator2, '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryText = queryText.replace(reOperator1, '');
|
||||||
|
}
|
||||||
|
if (m) {
|
||||||
|
let op;
|
||||||
|
if (m.groups.operator) {
|
||||||
|
op = m.groups.operator.toLowerCase();
|
||||||
|
} else {
|
||||||
|
op = m.groups.abbrev.toLowerCase();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (operatorMap.hasOwnProperty(op)) {
|
||||||
|
const operator = operatorMap[op];
|
||||||
|
let value = m.groups.value;
|
||||||
|
if (operator === OPERATOR_LABEL) {
|
||||||
|
if (value in this.colorMap) {
|
||||||
|
value = this.colorMap[value];
|
||||||
|
// console.log('found color:', value);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
[OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes(
|
||||||
|
operator,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const days = parseInt(value, 10);
|
||||||
|
let duration = null;
|
||||||
|
if (isNaN(days)) {
|
||||||
|
// duration was specified as text
|
||||||
|
if (predicateTranslations.durations[value]) {
|
||||||
|
duration = predicateTranslations.durations[value];
|
||||||
|
let date = null;
|
||||||
|
switch (duration) {
|
||||||
|
case PREDICATE_WEEK:
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const week = moment().week();
|
||||||
|
if (week === 52) {
|
||||||
|
date = moment(1, 'W');
|
||||||
|
date.set('year', date.year() + 1);
|
||||||
|
} else {
|
||||||
|
date = moment(week + 1, 'W');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PREDICATE_MONTH:
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const month = moment().month();
|
||||||
|
// .month() is zero indexed
|
||||||
|
if (month === 11) {
|
||||||
|
date = moment(1, 'M');
|
||||||
|
date.set('year', date.year() + 1);
|
||||||
|
} else {
|
||||||
|
date = moment(month + 2, 'M');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PREDICATE_QUARTER:
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const quarter = moment().quarter();
|
||||||
|
if (quarter === 4) {
|
||||||
|
date = moment(1, 'Q');
|
||||||
|
date.set('year', date.year() + 1);
|
||||||
|
} else {
|
||||||
|
date = moment(quarter + 1, 'Q');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PREDICATE_YEAR:
|
||||||
|
date = moment(moment().year() + 1, 'YYYY');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (date) {
|
||||||
|
value = {
|
||||||
|
operator: '$lt',
|
||||||
|
value: date.format('YYYY-MM-DD'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
operator === OPERATOR_DUE &&
|
||||||
|
value === PREDICATE_OVERDUE
|
||||||
|
) {
|
||||||
|
value = {
|
||||||
|
operator: '$lt',
|
||||||
|
value: moment().format('YYYY-MM-DD'),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.errors.addError(OPERATOR_DUE, {
|
||||||
|
tag: 'operator-number-expected',
|
||||||
|
value: { operator: op, value },
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (operator === OPERATOR_DUE) {
|
||||||
|
value = {
|
||||||
|
operator: '$lt',
|
||||||
|
value: moment(moment().format('YYYY-MM-DD'))
|
||||||
|
.add(days + 1, duration ? duration : 'days')
|
||||||
|
.format(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
value = {
|
||||||
|
operator: '$gte',
|
||||||
|
value: moment(moment().format('YYYY-MM-DD'))
|
||||||
|
.subtract(days, duration ? duration : 'days')
|
||||||
|
.format(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (operator === OPERATOR_SORT) {
|
||||||
|
let negated = false;
|
||||||
|
const m = value.match(reNegatedOperator);
|
||||||
|
if (m) {
|
||||||
|
value = m.groups.operator;
|
||||||
|
negated = true;
|
||||||
|
}
|
||||||
|
if (!predicateTranslations.sorts[value]) {
|
||||||
|
this.errors.addError(OPERATOR_SORT, {
|
||||||
|
tag: 'operator-sort-invalid',
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
value = {
|
||||||
|
name: predicateTranslations.sorts[value],
|
||||||
|
order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (operator === OPERATOR_STATUS) {
|
||||||
|
if (!predicateTranslations.status[value]) {
|
||||||
|
this.errors.addError(OPERATOR_STATUS, {
|
||||||
|
tag: 'operator-status-invalid',
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
value = predicateTranslations.status[value];
|
||||||
|
}
|
||||||
|
} else if (operator === OPERATOR_HAS) {
|
||||||
|
let negated = false;
|
||||||
|
const m = value.match(reNegatedOperator);
|
||||||
|
if (m) {
|
||||||
|
value = m.groups.operator;
|
||||||
|
negated = true;
|
||||||
|
}
|
||||||
|
if (!predicateTranslations.has[value]) {
|
||||||
|
this.errors.addError(OPERATOR_HAS, {
|
||||||
|
tag: 'operator-has-invalid',
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
value = {
|
||||||
|
field: predicateTranslations.has[value],
|
||||||
|
exists: !negated,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (operator === OPERATOR_LIMIT) {
|
||||||
|
const limit = parseInt(value, 10);
|
||||||
|
if (isNaN(limit) || limit < 1) {
|
||||||
|
this.errors.addError(OPERATOR_LIMIT, {
|
||||||
|
tag: 'operator-limit-invalid',
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
value = limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queryParams.addPredicate(operator, value);
|
||||||
|
} else {
|
||||||
|
this.errors.addError(OPERATOR_UNKNOWN, {
|
||||||
|
tag: 'operator-unknown-error',
|
||||||
|
value: op,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m = queryText.match(reQuotedText);
|
||||||
|
if (!m) {
|
||||||
|
m = queryText.match(reText);
|
||||||
|
if (m) {
|
||||||
|
queryText = queryText.replace(reText, '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryText = queryText.replace(reQuotedText, '');
|
||||||
|
}
|
||||||
|
if (m) {
|
||||||
|
text += (text ? ' ' : '') + m.groups.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
// console.log('text:', text);
|
||||||
|
this.queryParams.text = text;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('queryParams:', this.queryParams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import moment from 'moment';
|
||||||
import Users from '../../models/users';
|
import Users from '../../models/users';
|
||||||
import Boards from '../../models/boards';
|
import Boards from '../../models/boards';
|
||||||
import Lists from '../../models/lists';
|
import Lists from '../../models/lists';
|
||||||
import Swimlanes from '../../models/swimlanes';
|
import Swimlanes from '../../models/swimlanes';
|
||||||
|
import Cards from '../../models/cards';
|
||||||
import CardComments from '../../models/cardComments';
|
import CardComments from '../../models/cardComments';
|
||||||
import Attachments from '../../models/attachments';
|
import Attachments from '../../models/attachments';
|
||||||
import Checklists from '../../models/checklists';
|
import Checklists from '../../models/checklists';
|
||||||
|
|
@ -411,26 +413,26 @@ function buildSelector(queryParams) {
|
||||||
|
|
||||||
const attachments = Attachments.find({ 'original.name': regex });
|
const attachments = Attachments.find({ 'original.name': regex });
|
||||||
|
|
||||||
// const comments = CardComments.find(
|
const comments = CardComments.find(
|
||||||
// { text: regex },
|
{ text: regex },
|
||||||
// { fields: { cardId: 1 } },
|
{ fields: { cardId: 1 } },
|
||||||
// );
|
);
|
||||||
|
|
||||||
selector.$and.push({
|
selector.$and.push({
|
||||||
$or: [
|
$or: [
|
||||||
{ title: regex },
|
{ title: regex },
|
||||||
{ description: regex },
|
{ description: regex },
|
||||||
{ customFields: { $elemMatch: { value: regex } } },
|
{ customFields: { $elemMatch: { value: regex } } },
|
||||||
{
|
// {
|
||||||
_id: {
|
// _id: {
|
||||||
$in: CardComments.textSearch(userId, [queryParams.text]).map(
|
// $in: CardComments.textSearch(userId, [queryParams.text]).map(
|
||||||
com => com.cardId,
|
// com => com.cardId,
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{ _id: { $in: checklists.map(list => list.cardId) } },
|
{ _id: { $in: checklists.map(list => list.cardId) } },
|
||||||
{ _id: { $in: attachments.map(attach => attach.cardId) } },
|
{ _id: { $in: attachments.map(attach => attach.cardId) } },
|
||||||
// { _id: { $in: comments.map(com => com.cardId) } },
|
{ _id: { $in: comments.map(com => com.cardId) } },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue