diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade index 5389fb780..47fba719d 100644 --- a/client/components/main/globalSearch.jade +++ b/client/components/main/globalSearch.jade @@ -44,9 +44,9 @@ template(name="globalSearch") else if hasResults.get .global-search-results-list-wrapper if hasQueryErrors.get - div + ul each msg in errorMessages - span.global-search-error-messages + li.global-search-error-messages = msg else +resultsPaged(this) diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index e8b6870ac..22049cc12 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -1,16 +1,22 @@ import { CardSearchPagedComponent } from '../../lib/cardSearch'; +import Boards from '../../../models/boards'; import moment from 'moment'; 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, @@ -36,7 +42,7 @@ import { PREDICATE_WEEK, PREDICATE_YEAR, } from '../../../config/search-const'; -import { QueryParams } from "../../../config/query-classes"; +import { QueryErrors, QueryParams } from '../../../config/query-classes'; // const subManager = new SubsManager(); @@ -80,7 +86,7 @@ class GlobalSearchComponent extends CardSearchPagedComponent { this.myLists = new ReactiveVar([]); this.myLabelNames = new ReactiveVar([]); this.myBoardNames = new ReactiveVar([]); - this.parsingErrors = []; + this.parsingErrors = new QueryErrors(); this.colorMap = null; this.queryParams = null; @@ -119,26 +125,18 @@ class GlobalSearchComponent extends CardSearchPagedComponent { resetSearch() { super.resetSearch(); - this.parsingErrors = []; + this.parsingErrors = new QueryErrors(); } errorMessages() { - if (this.parsingErrors.length) { - return this.parsingErrorMessages(); + if (this.parsingErrors.hasErrors()) { + return this.parsingErrors.errorMessages(); } return this.queryErrorMessages(); } parsingErrorMessages() { - const messages = []; - - if (this.parsingErrors.length) { - this.parsingErrors.forEach(err => { - messages.push(TAPi18n.__(err.tag, err.value)); - }); - } - - return messages; + this.parsingErrors.errorMessages(); } searchAllBoards(query) { @@ -188,12 +186,12 @@ class GlobalSearchComponent extends CardSearchPagedComponent { 'operator-assignee-abbrev': OPERATOR_ASSIGNEE, 'operator-status': OPERATOR_STATUS, 'operator-due': OPERATOR_DUE, - 'operator-created': 'createdAt', - 'operator-modified': 'modifiedAt', - 'operator-comment': 'comments', + 'operator-created': OPERATOR_CREATED_AT, + 'operator-modified': OPERATOR_MODIFIED_AT, + 'operator-comment': OPERATOR_COMMENT, 'operator-has': OPERATOR_HAS, 'operator-sort': OPERATOR_SORT, - 'operator-limit': 'limit', + 'operator-limit': OPERATOR_LIMIT, }; const predicates = { @@ -247,29 +245,6 @@ class GlobalSearchComponent extends CardSearchPagedComponent { // eslint-disable-next-line no-console // console.log('operatorMap:', operatorMap); - // const params = { - // limit: this.resultsPerPage, - // // boards: [], - // // swimlanes: [], - // // lists: [], - // // users: [], - // members: [], - // assignees: [], - // // labels: [], - // status: [], - // // dueAt: null, - // createdAt: null, - // modifiedAt: null, - // comments: [], - // has: [], - // }; - // params[OPERATOR_BOARD] = []; - // params[OPERATOR_DUE] = null; - // params[OPERATOR_LABEL] = []; - // params[OPERATOR_LIST] = []; - // params[OPERATOR_SWIMLANE] = []; - // params[OPERATOR_USER] = []; - const params = new QueryParams(); let text = ''; while (query) { @@ -299,7 +274,9 @@ class GlobalSearchComponent extends CardSearchPagedComponent { // console.log('found color:', value); } } else if ( - [OPERATOR_DUE, 'createdAt', 'modifiedAt'].includes(operator) + [OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes( + operator, + ) ) { const days = parseInt(value, 10); let duration = null; @@ -350,19 +327,22 @@ class GlobalSearchComponent extends CardSearchPagedComponent { value: date.format('YYYY-MM-DD'), }; } - } else if (operator === 'dueAt' && value === PREDICATE_OVERDUE) { + } else if ( + operator === OPERATOR_DUE && + value === PREDICATE_OVERDUE + ) { value = { operator: '$lt', value: moment().format('YYYY-MM-DD'), }; } else { - this.parsingErrors.push({ + this.parsingErrors.addError(OPERATOR_DUE, { tag: 'operator-number-expected', value: { operator: op, value }, }); - value = null; + continue; } - } else if (operator === 'dueAt') { + } else if (operator === OPERATOR_DUE) { value = { operator: '$lt', value: moment(moment().format('YYYY-MM-DD')) @@ -385,10 +365,11 @@ class GlobalSearchComponent extends CardSearchPagedComponent { negated = true; } if (!predicateTranslations.sorts[value]) { - this.parsingErrors.push({ + this.parsingErrors.addError(OPERATOR_SORT, { tag: 'operator-sort-invalid', value, }); + continue; } else { value = { name: predicateTranslations.sorts[value], @@ -397,10 +378,11 @@ class GlobalSearchComponent extends CardSearchPagedComponent { } } else if (operator === OPERATOR_STATUS) { if (!predicateTranslations.status[value]) { - this.parsingErrors.push({ + this.parsingErrors.addError(OPERATOR_STATUS, { tag: 'operator-status-invalid', value, }); + continue; } else { value = predicateTranslations.status[value]; } @@ -412,23 +394,25 @@ class GlobalSearchComponent extends CardSearchPagedComponent { negated = true; } if (!predicateTranslations.has[value]) { - this.parsingErrors.push({ + this.parsingErrors.addError(OPERATOR_HAS, { tag: 'operator-has-invalid', value, }); + continue; } else { value = { field: predicateTranslations.has[value], exists: !negated, }; } - } else if (operator === 'limit') { + } else if (operator === OPERATOR_LIMIT) { const limit = parseInt(value, 10); if (isNaN(limit) || limit < 1) { - this.parsingErrors.push({ + this.parsingErrors.addError(OPERATOR_LIMIT, { tag: 'operator-limit-invalid', value, }); + continue; } else { value = limit; } @@ -436,7 +420,7 @@ class GlobalSearchComponent extends CardSearchPagedComponent { params.addPredicate(operator, value); } else { - this.parsingErrors.push({ + this.parsingErrors.addError(OPERATOR_UNKNOWN, { tag: 'operator-unknown-error', value: op, }); @@ -467,7 +451,7 @@ class GlobalSearchComponent extends CardSearchPagedComponent { this.queryParams = params; - if (this.parsingErrors.length) { + if (this.parsingErrors.hasErrors()) { this.searching.set(false); this.queryErrors = this.parsingErrorMessages(); this.hasResults.set(true); diff --git a/config/query-classes.js b/config/query-classes.js index f76109f45..ea597455e 100644 --- a/config/query-classes.js +++ b/config/query-classes.js @@ -8,9 +8,9 @@ import { OPERATOR_SWIMLANE, OPERATOR_USER, } from './search-const'; +import Boards from '../models/boards'; export class QueryParams { - text = ''; constructor(params = {}) { @@ -46,58 +46,81 @@ export class QueryParams { } 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'], + ]; + constructor() { - this.errors = {}; + this._errors = {}; + + this.operatorTags = {}; + this.operatorTagMap.forEach(([operator, tag]) => { + this.operatorTags[operator] = tag; + }); this.colorMap = Boards.colorMap(); } - addError(operator, value) { - if (!this.errors[operator]) { - this.errors[operator] = []; + 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 }); } - this.errors[operator].push(value) } hasErrors() { - return Object.entries(this.errors).length > 0; + return Object.entries(this._errors).length > 0; + } + + errors() { + const errs = []; + Object.entries(this._errors).forEach(([operator, errors]) => { + errors.forEach(err => { + errs.push(err); + }); + }); + return errs; } errorMessages() { const messages = []; - - const operatorTags = {}; - operatorTags[OPERATOR_BOARD] = 'board-title-not-found'; - operatorTags[OPERATOR_SWIMLANE] = 'swimlane-title-not-found'; - operatorTags[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, - }; - } - }; - operatorTags[OPERATOR_LIST] = 'list-title-not-found'; - operatorTags[OPERATOR_COMMENT] = 'comment-not-found'; - operatorTags[OPERATOR_USER] = 'user-username-not-found'; - operatorTags[OPERATOR_ASSIGNEE] = 'user-username-not-found'; - operatorTags[OPERATOR_MEMBER] = 'user-username-not-found'; - - Object.entries(this.errors, ([operator, value]) => { - if (typeof operatorTags[operator] === 'function') { - messages.push(operatorTags[operator](value)); - } else { - messages.push({ tag: operatorTags[operator], value: value }); - } + Object.entries(this._errors).forEach(([operator, errors]) => { + errors.forEach(err => { + messages.push(TAPi18n.__(err.tag, err.value)); + }); }); - return messages; } } @@ -106,9 +129,9 @@ export class Query { params = {}; selector = {}; projection = {}; - errors = new QueryErrors(); constructor(selector, projection) { + this._errors = new QueryErrors(); if (selector) { this.selector = selector; } @@ -117,4 +140,16 @@ export class Query { this.projection = projection; } } + + hasErrors() { + return this._errors.hasErrors(); + } + + errors() { + return this._errors.errors(); + } + + errorMessages() { + return this._errors.errorMessages(); + } } diff --git a/config/search-const.js b/config/search-const.js index fe16bee0e..26f8ad00b 100644 --- a/config/search-const.js +++ b/config/search-const.js @@ -1,14 +1,19 @@ +export const DEFAULT_LIMIT = 25; export const OPERATOR_ASSIGNEE = 'assignee'; export const OPERATOR_COMMENT = 'comment'; +export const OPERATOR_CREATED_AT = 'createdAt'; export const OPERATOR_DUE = 'dueAt'; export const OPERATOR_BOARD = 'board'; export const OPERATOR_HAS = 'has'; export const OPERATOR_LABEL = 'label'; +export const OPERATOR_LIMIT = 'limit'; export const OPERATOR_LIST = 'list'; export const OPERATOR_MEMBER = 'member'; +export const OPERATOR_MODIFIED_AT = 'modifiedAt'; export const OPERATOR_SORT = 'sort'; export const OPERATOR_STATUS = 'status'; export const OPERATOR_SWIMLANE = 'swimlane'; +export const OPERATOR_UNKNOWN = 'unknown'; export const OPERATOR_USER = 'user'; export const ORDER_ASCENDING = 'asc'; export const ORDER_DESCENDING = 'des'; diff --git a/server/publications/cards.js b/server/publications/cards.js index fc226b538..dfdf195e2 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -9,12 +9,14 @@ import ChecklistItems from '../../models/checklistItems'; import SessionData from '../../models/usersessiondata'; import CustomFields from '../../models/customFields'; import { + DEFAULT_LIMIT, OPERATOR_ASSIGNEE, OPERATOR_BOARD, OPERATOR_COMMENT, OPERATOR_DUE, OPERATOR_HAS, OPERATOR_LABEL, + OPERATOR_LIMIT, OPERATOR_LIST, OPERATOR_MEMBER, OPERATOR_SORT, @@ -81,7 +83,7 @@ Meteor.publish('globalSearch', function(sessionId, params) { check(params, Object); // eslint-disable-next-line no-console - // console.log('queryParams:', queryParams); + console.log('queryParams:', params); return findCards(sessionId, buildQuery(new QueryParams(params))); }); @@ -164,7 +166,7 @@ function buildSelector(queryParams) { queryBoards.push(board._id); }); } else { - errors.addError(OPERATOR_BOARD, query); + errors.addNotFound(OPERATOR_BOARD, query); } }); @@ -182,7 +184,7 @@ function buildSelector(queryParams) { querySwimlanes.push(swim._id); }); } else { - errors.addError(OPERATOR_SWIMLANE, query); + errors.addNotFound(OPERATOR_SWIMLANE, query); } }); @@ -204,7 +206,7 @@ function buildSelector(queryParams) { queryLists.push(list._id); }); } else { - errors.addError(OPERATOR_LIST, query); + errors.addNotFound(OPERATOR_LIST, query); } }); @@ -216,7 +218,9 @@ function buildSelector(queryParams) { } if (queryParams.hasOperator(OPERATOR_COMMENT)) { - const cardIds = CardComments.textSearch(userId, queryParams.getPredicates(OPERATOR_COMMENT)).map( + const cardIds = CardComments.textSearch( + userId, + queryParams.getPredicates(OPERATOR_COMMENT), com => { return com.cardId; }, @@ -225,7 +229,7 @@ function buildSelector(queryParams) { selector._id = { $in: cardIds }; } else { queryParams.getPredicates(OPERATOR_COMMENT).forEach(comment => { - errors.addError(OPERATOR_COMMENT, comment); + errors.addNotFound(OPERATOR_COMMENT, comment); }); } } @@ -238,7 +242,7 @@ function buildSelector(queryParams) { } }); - const queryUsers = {} + const queryUsers = {}; queryUsers[OPERATOR_ASSIGNEE] = []; queryUsers[OPERATOR_MEMBER] = []; @@ -253,7 +257,7 @@ function buildSelector(queryParams) { queryUsers[OPERATOR_ASSIGNEE].push(user._id); }); } else { - errors.addError(OPERATOR_USER, query); + errors.addNotFound(OPERATOR_USER, query); } }); } @@ -269,13 +273,16 @@ function buildSelector(queryParams) { queryUsers[key].push(user._id); }); } else { - errors.addError(key, query); + errors.addNotFound(key, query); } }); } }); - if (queryUsers[OPERATOR_MEMBER].length && queryUsers[OPERATOR_ASSIGNEE].length) { + if ( + queryUsers[OPERATOR_MEMBER].length && + queryUsers[OPERATOR_ASSIGNEE].length + ) { selector.$and.push({ $or: [ { members: { $in: queryUsers[OPERATOR_MEMBER] } }, @@ -334,7 +341,7 @@ function buildSelector(queryParams) { }); }); } else { - errors.addError(OPERATOR_LABEL, label); + errors.addNotFound(OPERATOR_LABEL, label); } } @@ -441,7 +448,7 @@ function buildSelector(queryParams) { const query = new Query(); query.selector = selector; query.params = queryParams; - query.errors = errors; + query._errors = errors; return query; } @@ -451,9 +458,9 @@ function buildProjection(query) { if (query.params.skip) { skip = query.params.skip; } - let limit = 25; - if (query.params.limit) { - limit = query.params.limit; + let limit = DEFAULT_LIMIT; + if (query.params.hasOperator(OPERATOR_LIMIT)) { + limit = query.params.getPredicate(OPERATOR_LIMIT); } const projection = { @@ -485,9 +492,12 @@ function buildProjection(query) { limit, }; - if (query.params[OPERATOR_SORT]) { - const order = query.params[OPERATOR_SORT].order === ORDER_ASCENDING ? 1 : -1; - switch (query.params[OPERATOR_SORT].name) { + if (query.params.hasOperator(OPERATOR_SORT)) { + const order = + query.params.getPredicate(OPERATOR_SORT).order === ORDER_ASCENDING + ? 1 + : -1; + switch (query.params.getPredicate(OPERATOR_SORT).name) { case PREDICATE_DUE_AT: projection.sort = { dueAt: order, @@ -586,12 +596,13 @@ function findCards(sessionId, query) { // eslint-disable-next-line no-console // console.log('projection:', projection); let cards; - if (!query.errors || !query.errors.hasErrors()) { + if (!query.hasErrors()) { cards = Cards.find(query.selector, query.projection); } // eslint-disable-next-line no-console // console.log('count:', cards.count()); + console.log(query); const update = { $set: { totalHits: 0, @@ -600,13 +611,15 @@ function findCards(sessionId, query) { cards: [], selector: SessionData.pickle(query.selector), projection: SessionData.pickle(query.projection), - errors: query.errors.errorMessages(), + errors: query.errors(), }, }; // if (errors) { - // update.$set.errors = errors.errorMessages(); + // update.$set.errors = errors.errors(); // } + console.log('errors:', query.errors()); + if (cards) { update.$set.totalHits = cards.count(); update.$set.lastHit =