123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- import moment from 'moment';
- import { TAPi18n } from '/imports/i18n';
- import {
- OPERATOR_ASSIGNEE,
- OPERATOR_BOARD,
- OPERATOR_COMMENT,
- OPERATOR_CREATED_AT,
- OPERATOR_CREATOR,
- OPERATOR_DEBUG,
- OPERATOR_DUE,
- OPERATOR_HAS,
- OPERATOR_LABEL,
- OPERATOR_LIMIT,
- OPERATOR_LIST,
- OPERATOR_MEMBER,
- OPERATOR_MODIFIED_AT,
- OPERATOR_ORG,
- OPERATOR_SORT,
- OPERATOR_STATUS,
- OPERATOR_SWIMLANE,
- OPERATOR_TEAM,
- 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_PROJECTION,
- PREDICATE_PUBLIC,
- PREDICATE_QUARTER,
- PREDICATE_SELECTOR,
- PREDICATE_START_AT,
- PREDICATE_WEEK,
- PREDICATE_YEAR,
- } from './search-const';
- import Boards from '../models/boards';
- export class QueryDebug {
- predicate = null;
- constructor(predicate) {
- if (predicate) {
- this.set(predicate)
- }
- }
- get() {
- return this.predicate;
- }
- set(predicate) {
- if ([PREDICATE_ALL, PREDICATE_SELECTOR, PREDICATE_PROJECTION].includes(
- predicate
- )) {
- this.predicate = predicate;
- } else {
- this.predicate = null;
- }
- }
- show() {
- return (this.predicate !== null);
- }
- showAll() {
- return (this.predicate === PREDICATE_ALL);
- }
- showSelector() {
- return (this.predicate === PREDICATE_ALL || this.predicate === PREDICATE_SELECTOR);
- }
- showProjection() {
- return (this.predicate === PREDICATE_ALL || this.predicate === PREDICATE_PROJECTION);
- }
- }
- export class QueryParams {
- text = '';
- constructor(params = {}, text = '') {
- this.params = params;
- this.text = text;
- }
- hasOperator(operator) {
- return (
- this.params[operator] !== undefined &&
- (this.params[operator].length === undefined ||
- this.params[operator].length > 0)
- );
- }
- addPredicate(operator, predicate) {
- if (!this.hasOperator(operator)) {
- this.params[operator] = [];
- }
- this.params[operator].push(predicate);
- }
- setPredicate(operator, predicate) {
- this.params[operator] = predicate;
- }
- getPredicate(operator) {
- if (this.hasOperator(operator)){
- if (typeof this.params[operator] === 'object') {
- return this.params[operator][0];
- } else {
- return this.params[operator];
- }
- }
- return null;
- }
- getPredicates(operator) {
- return this.params[operator];
- }
- getParams() {
- return this.params;
- }
- }
- export class QueryErrors {
- operatorTagMap = [
- [OPERATOR_BOARD, 'board-title-not-found'],
- [OPERATOR_SWIMLANE, 'swimlane-title-not-found'],
- [
- OPERATOR_LABEL,
- label => {
- if (Boards.labelColors().includes(label)) {
- return {
- tag: 'label-color-not-found',
- value: label,
- color: true,
- };
- } else {
- return {
- tag: 'label-not-found',
- value: label,
- color: false,
- };
- }
- },
- ],
- [OPERATOR_LIST, 'list-title-not-found'],
- [OPERATOR_COMMENT, 'comment-not-found'],
- [OPERATOR_USER, 'user-username-not-found'],
- [OPERATOR_ASSIGNEE, 'user-username-not-found'],
- [OPERATOR_MEMBER, 'user-username-not-found'],
- [OPERATOR_CREATOR, 'user-username-not-found'],
- [OPERATOR_ORG, 'org-name-not-found'],
- [OPERATOR_TEAM, 'team-name-not-found'],
- ];
- constructor() {
- this._errors = {};
- this.operatorTags = {};
- this.operatorTagMap.forEach(([operator, tag]) => {
- this.operatorTags[operator] = tag;
- });
- this.colorMap = Boards.colorMap();
- }
- addError(operator, error) {
- if (!this._errors[operator]) {
- this._errors[operator] = [];
- }
- this._errors[operator].push(error);
- }
- addNotFound(operator, value) {
- if (typeof this.operatorTags[operator] === 'function') {
- this.addError(operator, this.operatorTags[operator](value));
- } else {
- this.addError(operator, { tag: this.operatorTags[operator], value });
- }
- }
- hasErrors() {
- return Object.entries(this._errors).length > 0;
- }
- errors() {
- const errs = [];
- // eslint-disable-next-line no-unused-vars
- Object.entries(this._errors).forEach(([, errors]) => {
- errors.forEach(err => {
- errs.push(err);
- });
- });
- return errs;
- }
- errorMessages() {
- const messages = [];
- // eslint-disable-next-line no-unused-vars
- Object.entries(this._errors).forEach(([, errors]) => {
- errors.forEach(err => {
- messages.push(TAPi18n.__(err.tag, err.value));
- });
- });
- return messages;
- }
- }
- export class Query {
- selector = {};
- projection = {};
- constructor(selector, projection) {
- this._errors = new QueryErrors();
- this.queryParams = new QueryParams();
- this.colorMap = Boards.colorMap();
- if (selector) {
- this.selector = selector;
- }
- if (projection) {
- this.projection = projection;
- }
- }
- hasErrors() {
- return this._errors.hasErrors();
- }
- errors() {
- return this._errors.errors();
- }
- addError(operator, error) {
- this._errors.addError(operator, error)
- }
- errorMessages() {
- return this._errors.errorMessages();
- }
- getQueryParams() {
- return this.queryParams;
- }
- setQueryParams(queryParams) {
- this.queryParams = queryParams;
- }
- addPredicate(operator, predicate) {
- this.queryParams.addPredicate(operator, predicate);
- }
- buildParams(queryText) {
- this.queryParams = new QueryParams();
- 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-creator': OPERATOR_CREATOR,
- '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,
- 'operator-debug': OPERATOR_DEBUG,
- 'operator-org': OPERATOR_ORG,
- 'operator-team': OPERATOR_TEAM,
- };
- const predicates = {
- durations: {
- 'predicate-week': PREDICATE_WEEK,
- 'predicate-month': PREDICATE_MONTH,
- 'predicate-quarter': PREDICATE_QUARTER,
- 'predicate-year': PREDICATE_YEAR,
- },
- };
- predicates[OPERATOR_DUE] = {
- 'predicate-overdue': PREDICATE_OVERDUE,
- };
- predicates[OPERATOR_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,
- };
- predicates[OPERATOR_SORT] = {
- 'predicate-due': PREDICATE_DUE_AT,
- 'predicate-created': PREDICATE_CREATED_AT,
- 'predicate-modified': PREDICATE_MODIFIED_AT,
- };
- predicates[OPERATOR_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,
- };
- predicates[OPERATOR_DEBUG] = {
- 'predicate-all': PREDICATE_ALL,
- 'predicate-selector': PREDICATE_SELECTOR,
- 'predicate-projection': PREDICATE_PROJECTION,
- };
- 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.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[OPERATOR_SORT][value]) {
- this.addError(OPERATOR_SORT, {
- tag: 'operator-sort-invalid',
- value,
- });
- continue;
- } else {
- value = {
- name: predicateTranslations[OPERATOR_SORT][value],
- order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
- };
- }
- } else if (operator === OPERATOR_STATUS) {
- if (!predicateTranslations[OPERATOR_STATUS][value]) {
- this.addError(OPERATOR_STATUS, {
- tag: 'operator-status-invalid',
- value,
- });
- continue;
- } else {
- value = predicateTranslations[OPERATOR_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[OPERATOR_HAS][value]) {
- this.addError(OPERATOR_HAS, {
- tag: 'operator-has-invalid',
- value,
- });
- continue;
- } else {
- value = {
- field: predicateTranslations[OPERATOR_HAS][value],
- exists: !negated,
- };
- }
- } else if (operator === OPERATOR_LIMIT) {
- const limit = parseInt(value, 10);
- if (isNaN(limit) || limit < 1) {
- this.addError(OPERATOR_LIMIT, {
- tag: 'operator-limit-invalid',
- value,
- });
- continue;
- } else {
- value = limit;
- }
- } else if (operator === OPERATOR_DEBUG) {
- if (!predicateTranslations[OPERATOR_DEBUG][value]) {
- this.addError(OPERATOR_DEBUG, {
- tag: 'operator-debug-invalid',
- value,
- });
- continue;
- } else {
- value = predicateTranslations[OPERATOR_DEBUG][value];
- }
- }
- this.queryParams.addPredicate(operator, value);
- } else {
- this.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;
- }
- }
- this.queryParams.text = text;
- // eslint-disable-next-line no-console
- if (this.queryParams.hasOperator(OPERATOR_DEBUG)) {
- // eslint-disable-next-line no-console
- console.log('text:', this.queryParams.text);
- console.log('queryParams:', this.queryParams);
- }
- }
- }
|