mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
Development
* Generate error when a comment text is not found * Save errors to SessionData as objects * Move all search code to globalSearch publication * Add more translation tags
This commit is contained in:
parent
211d27352a
commit
158a0807d9
8 changed files with 452 additions and 409 deletions
|
|
@ -19,3 +19,6 @@
|
|||
|
||||
.result-card-context-list
|
||||
margin-bottom: 0.7rem
|
||||
|
||||
.result-card-block-wrapper
|
||||
display: inline-block
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ template(name="globalSearch")
|
|||
.global-search-results-list-wrapper
|
||||
if hasQueryErrors.get
|
||||
div
|
||||
each msg in queryErrors
|
||||
each msg in errorMessages
|
||||
span.global-search-error-messages
|
||||
= msg
|
||||
else
|
||||
|
|
|
|||
|
|
@ -52,13 +52,6 @@ BlazeComponent.extendComponent({
|
|||
this.totalHits = 0;
|
||||
this.queryErrors = null;
|
||||
this.colorMap = null;
|
||||
// this.colorMap = {};
|
||||
// for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
// .allowedValues) {
|
||||
// this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
// }
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('colorMap:', this.colorMap);
|
||||
|
||||
Meteor.call('myLists', (err, data) => {
|
||||
if (!err) {
|
||||
|
|
@ -81,6 +74,15 @@ BlazeComponent.extendComponent({
|
|||
|
||||
onRendered() {
|
||||
Meteor.subscribe('setting');
|
||||
|
||||
this.colorMap = {};
|
||||
for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
.allowedValues) {
|
||||
this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
}
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('colorMap:', this.colorMap);
|
||||
|
||||
if (Session.get('globalQuery')) {
|
||||
this.searchAllBoards(Session.get('globalQuery'));
|
||||
}
|
||||
|
|
@ -107,17 +109,10 @@ BlazeComponent.extendComponent({
|
|||
sessionId: SessionData.getSessionId(),
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('session data:', sessionData);
|
||||
// console.log('session data:', sessionData);
|
||||
|
||||
const cards = Cards.find({ _id: { $in: sessionData.cards } });
|
||||
this.queryErrors = sessionData.errorMessages;
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('errors:', this.queryErrors);
|
||||
if (this.parsingErrors.length) {
|
||||
this.queryErrors = this.errorMessages();
|
||||
this.hasQueryErrors.set(true);
|
||||
return null;
|
||||
}
|
||||
this.queryErrors = sessionData.errors;
|
||||
if (this.queryErrors.length) {
|
||||
this.hasQueryErrors.set(true);
|
||||
return null;
|
||||
|
|
@ -135,6 +130,13 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
errorMessages() {
|
||||
if (this.parsingErrors.length) {
|
||||
return this.parsingErrorMessages();
|
||||
}
|
||||
return this.queryErrorMessages();
|
||||
},
|
||||
|
||||
parsingErrorMessages() {
|
||||
const messages = [];
|
||||
|
||||
if (this.parsingErrors.length) {
|
||||
|
|
@ -146,6 +148,20 @@ BlazeComponent.extendComponent({
|
|||
return messages;
|
||||
},
|
||||
|
||||
queryErrorMessages() {
|
||||
messages = [];
|
||||
|
||||
this.queryErrors.forEach(err => {
|
||||
let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value;
|
||||
if (!value) {
|
||||
value = err.value;
|
||||
}
|
||||
messages.push(TAPi18n.__(err.tag, value));
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
searchAllBoards(query) {
|
||||
query = query.trim();
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
@ -161,14 +177,6 @@ BlazeComponent.extendComponent({
|
|||
|
||||
this.searching.set(true);
|
||||
|
||||
if (!this.colorMap) {
|
||||
this.colorMap = {};
|
||||
for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
.allowedValues) {
|
||||
this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
const reOperator1 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<value>\w+)(\s+|$)/;
|
||||
const reOperator2 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/;
|
||||
const reText = /^(?<text>\S+)(\s+|$)/;
|
||||
|
|
@ -200,9 +208,9 @@ BlazeComponent.extendComponent({
|
|||
Object.entries(operators).forEach(([key, value]) => {
|
||||
operatorMap[TAPi18n.__(key).toLowerCase()] = value;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('operatorMap:', operatorMap);
|
||||
|
||||
const params = {
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
|
|
@ -315,13 +323,13 @@ BlazeComponent.extendComponent({
|
|||
params.text = text;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('params:', params);
|
||||
// console.log('params:', params);
|
||||
|
||||
this.queryParams = params;
|
||||
|
||||
if (this.parsingErrors.length) {
|
||||
this.searching.set(false);
|
||||
this.queryErrors = this.errorMessages();
|
||||
this.queryErrors = this.parsingErrorMessages();
|
||||
this.hasQueryErrors.set(true);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -876,6 +876,7 @@
|
|||
"label-not-found": "Label '%s' not found.",
|
||||
"label-color-not-found": "Label color %s not found.",
|
||||
"user-username-not-found": "Username '%s' not found.",
|
||||
"comment-not-found": "Card with comment containing text '%s' not found.",
|
||||
"globalSearch-title": "Search All Boards",
|
||||
"no-cards-found": "No Cards Found",
|
||||
"one-card-found": "One Card Found",
|
||||
|
|
@ -901,6 +902,9 @@
|
|||
"operator-modified": "modified",
|
||||
"operator-sort": "sort",
|
||||
"operator-comment": "comment",
|
||||
"predicate-archived": "archived",
|
||||
"predicate-active": "active",
|
||||
"predicate-overdue": "overdue",
|
||||
"operator-unknown-error": "%s is not an operator",
|
||||
"operator-number-expected": "operator __operator__ expected a number, got '__value__'",
|
||||
"operator-sort-invalid": "sort of '%s' is invalid",
|
||||
|
|
@ -911,12 +915,16 @@
|
|||
"globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
|
||||
"globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
|
||||
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
|
||||
"globalSearch-instructions-operator-comment": "`__operator_comment__:text` - cards with with a comment containing *text*.",
|
||||
"globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
|
||||
"globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
|
||||
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
|
||||
"globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
|
||||
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
|
||||
"globalSearch-instructions-operator-due": "`__operator_due__:n` - cards which are due *n* days from now. `__operator_due__:__predicate_overdue__ lists all ",
|
||||
"globalSearch-instructions-operator-created": "`__operator_created__:n` - cards which which were created *n* days ago",
|
||||
"globalSearch-instructions-operator-modified": "`__operator_modified__:n` - cards which which were modified *n* days ago",
|
||||
"globalSearch-instructions-notes-1": "Multiple operators may be specified.",
|
||||
"globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
|
||||
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
|
||||
|
|
|
|||
|
|
@ -125,7 +125,10 @@ CardComments.textSearch = (userId, textArray) => {
|
|||
|
||||
const comments = CardComments.find(selector);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('count:', comments.count());
|
||||
// console.log('count:', comments.count());
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cards with comments:', comments.map(com => { return com.cardId }));
|
||||
|
||||
return comments;
|
||||
};
|
||||
|
||||
|
|
|
|||
351
models/cards.js
351
models/cards.js
|
|
@ -1,5 +1,3 @@
|
|||
const escapeForRegex = require('escape-string-regexp');
|
||||
|
||||
Cards = new Mongo.Collection('cards');
|
||||
|
||||
// XXX To improve pub/sub performances a card document should include a
|
||||
|
|
@ -1865,355 +1863,6 @@ Cards.mutations({
|
|||
},
|
||||
});
|
||||
|
||||
Cards.globalSearch = queryParams => {
|
||||
const userId = Meteor.userId();
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('userId:', userId);
|
||||
|
||||
const errors = new (class {
|
||||
constructor() {
|
||||
this.notFound = {
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
lists: [],
|
||||
labels: [],
|
||||
users: [],
|
||||
members: [],
|
||||
assignees: [],
|
||||
is: [],
|
||||
};
|
||||
|
||||
this.colorMap = {};
|
||||
for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
.allowedValues) {
|
||||
this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
hasErrors() {
|
||||
for (const prop in this.notFound) {
|
||||
if (this.notFound[prop].length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessages() {
|
||||
const messages = [];
|
||||
|
||||
this.notFound.boards.forEach(board => {
|
||||
messages.push(TAPi18n.__('board-title-not-found', board));
|
||||
});
|
||||
this.notFound.swimlanes.forEach(swim => {
|
||||
messages.push(TAPi18n.__('swimlane-title-not-found', swim));
|
||||
});
|
||||
this.notFound.lists.forEach(list => {
|
||||
messages.push(TAPi18n.__('list-title-not-found', list));
|
||||
});
|
||||
this.notFound.labels.forEach(label => {
|
||||
const color = Object.entries(this.colorMap)
|
||||
.filter(value => value[1] === label)
|
||||
.map(value => value[0]);
|
||||
if (color.length) {
|
||||
messages.push(TAPi18n.__('label-color-not-found', color[0]));
|
||||
} else {
|
||||
messages.push(TAPi18n.__('label-not-found', label));
|
||||
}
|
||||
});
|
||||
this.notFound.users.forEach(user => {
|
||||
messages.push(TAPi18n.__('user-username-not-found', user));
|
||||
});
|
||||
this.notFound.members.forEach(user => {
|
||||
messages.push(TAPi18n.__('user-username-not-found', user));
|
||||
});
|
||||
this.notFound.assignees.forEach(user => {
|
||||
messages.push(TAPi18n.__('user-username-not-found', user));
|
||||
});
|
||||
|
||||
return messages;
|
||||
}
|
||||
})();
|
||||
|
||||
const selector = {
|
||||
archived: false,
|
||||
type: 'cardType-card',
|
||||
boardId: { $in: Boards.userBoardIds(userId) },
|
||||
swimlaneId: { $nin: Swimlanes.archivedSwimlaneIds() },
|
||||
listId: { $nin: Lists.archivedListIds() },
|
||||
};
|
||||
|
||||
if (queryParams.boards.length) {
|
||||
const queryBoards = [];
|
||||
queryParams.boards.forEach(query => {
|
||||
const boards = Boards.userSearch(userId, {
|
||||
title: new RegExp(escapeForRegex(query), 'i'),
|
||||
});
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
queryBoards.push(board._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.boards.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.boardId.$in = queryBoards;
|
||||
}
|
||||
|
||||
if (queryParams.swimlanes.length) {
|
||||
const querySwimlanes = [];
|
||||
queryParams.swimlanes.forEach(query => {
|
||||
const swimlanes = Swimlanes.find({
|
||||
title: new RegExp(escapeForRegex(query), 'i'),
|
||||
});
|
||||
if (swimlanes.count()) {
|
||||
swimlanes.forEach(swim => {
|
||||
querySwimlanes.push(swim._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.swimlanes.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.swimlaneId.$in = querySwimlanes;
|
||||
}
|
||||
|
||||
if (queryParams.lists.length) {
|
||||
const queryLists = [];
|
||||
queryParams.lists.forEach(query => {
|
||||
const lists = Lists.find({
|
||||
title: new RegExp(escapeForRegex(query), 'i'),
|
||||
});
|
||||
if (lists.count()) {
|
||||
lists.forEach(list => {
|
||||
queryLists.push(list._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.lists.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.listId.$in = queryLists;
|
||||
}
|
||||
|
||||
if (queryParams.comments.length) {
|
||||
selector._id = {
|
||||
$in: CardComments.textSearch(userId, queryParams.comments).map(com => {
|
||||
return com.cardId;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (queryParams.dueAt !== null) {
|
||||
selector.dueAt = { $lte: new Date(queryParams.dueAt) };
|
||||
}
|
||||
|
||||
if (queryParams.createdAt !== null) {
|
||||
selector.createdAt = { $gte: new Date(queryParams.createdAt) };
|
||||
}
|
||||
|
||||
if (queryParams.modifiedAt !== null) {
|
||||
selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) };
|
||||
}
|
||||
|
||||
const queryMembers = [];
|
||||
const queryAssignees = [];
|
||||
if (queryParams.users.length) {
|
||||
queryParams.users.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryMembers.push(user._id);
|
||||
queryAssignees.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.users.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.members.length) {
|
||||
queryParams.members.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryMembers.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.members.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.assignees.length) {
|
||||
queryParams.assignees.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryAssignees.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.assignees.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryMembers.length && queryAssignees.length) {
|
||||
selector.$or = [
|
||||
{ members: { $in: queryMembers } },
|
||||
{ assignees: { $in: queryAssignees } },
|
||||
];
|
||||
} else if (queryMembers.length) {
|
||||
selector.members = { $in: queryMembers };
|
||||
} else if (queryAssignees.length) {
|
||||
selector.assignees = { $in: queryAssignees };
|
||||
}
|
||||
|
||||
if (queryParams.labels.length) {
|
||||
queryParams.labels.forEach(label => {
|
||||
const queryLabels = [];
|
||||
|
||||
let boards = Boards.userSearch(userId, {
|
||||
labels: { $elemMatch: { color: label.toLowerCase() } },
|
||||
});
|
||||
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board:', board);
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board.labels:', board.labels);
|
||||
board.labels
|
||||
.filter(boardLabel => {
|
||||
return boardLabel.color === label.toLowerCase();
|
||||
})
|
||||
.forEach(boardLabel => {
|
||||
queryLabels.push(boardLabel._id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('label:', label);
|
||||
const reLabel = new RegExp(escapeForRegex(label), 'i');
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('reLabel:', reLabel);
|
||||
boards = Boards.userSearch(userId, {
|
||||
labels: { $elemMatch: { name: reLabel } },
|
||||
});
|
||||
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
board.labels
|
||||
.filter(boardLabel => {
|
||||
return boardLabel.name.match(reLabel);
|
||||
})
|
||||
.forEach(boardLabel => {
|
||||
queryLabels.push(boardLabel._id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
errors.notFound.labels.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
selector.labelIds = { $in: queryLabels };
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
return { cards: null, errors };
|
||||
}
|
||||
|
||||
if (queryParams.text) {
|
||||
const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
|
||||
|
||||
selector.$or = [
|
||||
{ title: regex },
|
||||
{ description: regex },
|
||||
{ customFields: { $elemMatch: { value: regex } } },
|
||||
{
|
||||
_id: {
|
||||
$in: CardComments.textSearch(userId, [queryParams.text]).map(
|
||||
com => com.cardId,
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('selector:', selector);
|
||||
|
||||
const projection = {
|
||||
fields: {
|
||||
_id: 1,
|
||||
archived: 1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
title: 1,
|
||||
type: 1,
|
||||
sort: 1,
|
||||
members: 1,
|
||||
assignees: 1,
|
||||
colors: 1,
|
||||
dueAt: 1,
|
||||
createdAt: 1,
|
||||
modifiedAt: 1,
|
||||
labelIds: 1,
|
||||
},
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
if (queryParams.sort === 'due') {
|
||||
projection.sort = {
|
||||
dueAt: 1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
} else if (queryParams.sort === 'modified') {
|
||||
projection.sort = {
|
||||
modifiedAt: -1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
} else if (queryParams.sort === 'created') {
|
||||
projection.sort = {
|
||||
createdAt: -1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
} else if (queryParams.sort === 'system') {
|
||||
projection.sort = {
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
modifiedAt: 1,
|
||||
sort: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const cards = Cards.find(selector, projection);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('count:', cards.count());
|
||||
|
||||
return { cards, errors };
|
||||
};
|
||||
|
||||
//FUNCTIONS FOR creation of Activities
|
||||
|
||||
function updateActivities(doc, fieldNames, modifier) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,35 @@ SessionData.attachSchema(
|
|||
type: [String],
|
||||
optional: true,
|
||||
},
|
||||
errors: {
|
||||
type: [Object],
|
||||
optional: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
'errors.$': {
|
||||
type: new SimpleSchema({
|
||||
tag: {
|
||||
/**
|
||||
* i18n tag
|
||||
*/
|
||||
type: String,
|
||||
optional: false,
|
||||
},
|
||||
value: {
|
||||
/**
|
||||
* value for the tag
|
||||
*/
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
color: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
createdAt: {
|
||||
/**
|
||||
* creation date of the team
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
const escapeForRegex = require('escape-string-regexp');
|
||||
|
||||
Meteor.publish('card', cardId => {
|
||||
check(cardId, String);
|
||||
return Cards.find({ _id: cardId });
|
||||
|
|
@ -177,18 +179,363 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
check(sessionId, String);
|
||||
check(queryParams, Object);
|
||||
|
||||
const userId = Meteor.userId();
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('queryParams:', queryParams);
|
||||
// console.log('userId:', userId);
|
||||
|
||||
const results = Cards.globalSearch(queryParams);
|
||||
const cards = results.cards;
|
||||
const errors = new (class {
|
||||
constructor() {
|
||||
this.notFound = {
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
lists: [],
|
||||
labels: [],
|
||||
users: [],
|
||||
members: [],
|
||||
assignees: [],
|
||||
is: [],
|
||||
comments: [],
|
||||
};
|
||||
|
||||
this.colorMap = {};
|
||||
for (const color of Boards.simpleSchema()._schema['labels.$.color']
|
||||
.allowedValues) {
|
||||
this.colorMap[TAPi18n.__(`color-${color}`)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
hasErrors() {
|
||||
for (const prop in this.notFound) {
|
||||
if (this.notFound[prop].length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessages() {
|
||||
const messages = [];
|
||||
|
||||
this.notFound.boards.forEach(board => {
|
||||
messages.push({ tag: 'board-title-not-found', value: board });
|
||||
});
|
||||
this.notFound.swimlanes.forEach(swim => {
|
||||
messages.push({ tag: 'swimlane-title-not-found', value: swim });
|
||||
});
|
||||
this.notFound.lists.forEach(list => {
|
||||
messages.push({ tag: 'list-title-not-found', value: list });
|
||||
});
|
||||
this.notFound.comments.forEach(comments => {
|
||||
comments.forEach(text => {
|
||||
messages.push({ tag: 'comment-not-found', value: text });
|
||||
});
|
||||
});
|
||||
this.notFound.labels.forEach(label => {
|
||||
messages.push({ tag: 'label-not-found', value: label, color: true });
|
||||
});
|
||||
this.notFound.users.forEach(user => {
|
||||
messages.push({ tag: 'user-username-not-found', value: user });
|
||||
});
|
||||
this.notFound.members.forEach(user => {
|
||||
messages.push({ tag: 'user-username-not-found', value: user });
|
||||
});
|
||||
this.notFound.assignees.forEach(user => {
|
||||
messages.push({ tag: 'user-username-not-found', value: user });
|
||||
});
|
||||
|
||||
return messages;
|
||||
}
|
||||
})();
|
||||
|
||||
const selector = {
|
||||
archived: false,
|
||||
type: 'cardType-card',
|
||||
boardId: { $in: Boards.userBoardIds(userId) },
|
||||
swimlaneId: { $nin: Swimlanes.archivedSwimlaneIds() },
|
||||
listId: { $nin: Lists.archivedListIds() },
|
||||
};
|
||||
|
||||
if (queryParams.boards.length) {
|
||||
const queryBoards = [];
|
||||
queryParams.boards.forEach(query => {
|
||||
const boards = Boards.userSearch(userId, {
|
||||
title: new RegExp(escapeForRegex(query), 'i'),
|
||||
});
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
queryBoards.push(board._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.boards.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.boardId.$in = queryBoards;
|
||||
}
|
||||
|
||||
if (queryParams.swimlanes.length) {
|
||||
const querySwimlanes = [];
|
||||
queryParams.swimlanes.forEach(query => {
|
||||
const swimlanes = Swimlanes.find({
|
||||
title: new RegExp(escapeForRegex(query), 'i'),
|
||||
});
|
||||
if (swimlanes.count()) {
|
||||
swimlanes.forEach(swim => {
|
||||
querySwimlanes.push(swim._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.swimlanes.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.swimlaneId.$in = querySwimlanes;
|
||||
}
|
||||
|
||||
if (queryParams.lists.length) {
|
||||
const queryLists = [];
|
||||
queryParams.lists.forEach(query => {
|
||||
const lists = Lists.find({
|
||||
title: new RegExp(escapeForRegex(query), 'i'),
|
||||
});
|
||||
if (lists.count()) {
|
||||
lists.forEach(list => {
|
||||
queryLists.push(list._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.lists.push(query);
|
||||
}
|
||||
});
|
||||
|
||||
selector.listId.$in = queryLists;
|
||||
}
|
||||
|
||||
if (queryParams.comments.length) {
|
||||
const cardIds = CardComments.textSearch(userId, queryParams.comments).map(
|
||||
com => {
|
||||
return com.cardId;
|
||||
},
|
||||
);
|
||||
if (cardIds.length) {
|
||||
selector._id = { $in: cardIds };
|
||||
} else {
|
||||
errors.notFound.comments.push(queryParams.comments);
|
||||
}
|
||||
}
|
||||
|
||||
if (queryParams.dueAt !== null) {
|
||||
selector.dueAt = { $lte: new Date(queryParams.dueAt) };
|
||||
}
|
||||
|
||||
if (queryParams.createdAt !== null) {
|
||||
selector.createdAt = { $gte: new Date(queryParams.createdAt) };
|
||||
}
|
||||
|
||||
if (queryParams.modifiedAt !== null) {
|
||||
selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) };
|
||||
}
|
||||
|
||||
const queryMembers = [];
|
||||
const queryAssignees = [];
|
||||
if (queryParams.users.length) {
|
||||
queryParams.users.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryMembers.push(user._id);
|
||||
queryAssignees.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.users.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.members.length) {
|
||||
queryParams.members.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryMembers.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.members.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.assignees.length) {
|
||||
queryParams.assignees.forEach(query => {
|
||||
const users = Users.find({
|
||||
username: query,
|
||||
});
|
||||
if (users.count()) {
|
||||
users.forEach(user => {
|
||||
queryAssignees.push(user._id);
|
||||
});
|
||||
} else {
|
||||
errors.notFound.assignees.push(query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryMembers.length && queryAssignees.length) {
|
||||
selector.$or = [
|
||||
{ members: { $in: queryMembers } },
|
||||
{ assignees: { $in: queryAssignees } },
|
||||
];
|
||||
} else if (queryMembers.length) {
|
||||
selector.members = { $in: queryMembers };
|
||||
} else if (queryAssignees.length) {
|
||||
selector.assignees = { $in: queryAssignees };
|
||||
}
|
||||
|
||||
if (queryParams.labels.length) {
|
||||
queryParams.labels.forEach(label => {
|
||||
const queryLabels = [];
|
||||
|
||||
let boards = Boards.userSearch(userId, {
|
||||
labels: { $elemMatch: { color: label.toLowerCase() } },
|
||||
});
|
||||
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board:', board);
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board.labels:', board.labels);
|
||||
board.labels
|
||||
.filter(boardLabel => {
|
||||
return boardLabel.color === label.toLowerCase();
|
||||
})
|
||||
.forEach(boardLabel => {
|
||||
queryLabels.push(boardLabel._id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('label:', label);
|
||||
const reLabel = new RegExp(escapeForRegex(label), 'i');
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('reLabel:', reLabel);
|
||||
boards = Boards.userSearch(userId, {
|
||||
labels: { $elemMatch: { name: reLabel } },
|
||||
});
|
||||
|
||||
if (boards.count()) {
|
||||
boards.forEach(board => {
|
||||
board.labels
|
||||
.filter(boardLabel => {
|
||||
return boardLabel.name.match(reLabel);
|
||||
})
|
||||
.forEach(boardLabel => {
|
||||
queryLabels.push(boardLabel._id);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
errors.notFound.labels.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
selector.labelIds = { $in: queryLabels };
|
||||
});
|
||||
}
|
||||
|
||||
let cards = null;
|
||||
|
||||
if (!errors.hasErrors()) {
|
||||
if (queryParams.text) {
|
||||
const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
|
||||
|
||||
selector.$or = [
|
||||
{ title: regex },
|
||||
{ description: regex },
|
||||
{ customFields: { $elemMatch: { value: regex } } },
|
||||
{
|
||||
_id: {
|
||||
$in: CardComments.textSearch(userId, [queryParams.text]).map(
|
||||
com => com.cardId,
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('selector:', selector);
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('selector.$or:', selector.$or);
|
||||
|
||||
const projection = {
|
||||
fields: {
|
||||
_id: 1,
|
||||
archived: 1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
title: 1,
|
||||
type: 1,
|
||||
sort: 1,
|
||||
members: 1,
|
||||
assignees: 1,
|
||||
colors: 1,
|
||||
dueAt: 1,
|
||||
createdAt: 1,
|
||||
modifiedAt: 1,
|
||||
labelIds: 1,
|
||||
},
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
if (queryParams.sort === 'due') {
|
||||
projection.sort = {
|
||||
dueAt: 1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
} else if (queryParams.sort === 'modified') {
|
||||
projection.sort = {
|
||||
modifiedAt: -1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
} else if (queryParams.sort === 'created') {
|
||||
projection.sort = {
|
||||
createdAt: -1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
} else if (queryParams.sort === 'system') {
|
||||
projection.sort = {
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
modifiedAt: 1,
|
||||
sort: 1,
|
||||
};
|
||||
}
|
||||
|
||||
cards = Cards.find(selector, projection);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('count:', cards.count());
|
||||
}
|
||||
|
||||
const update = {
|
||||
$set: {
|
||||
totalHits: 0,
|
||||
lastHit: 0,
|
||||
cards: [],
|
||||
errorMessages: results.errors.errorMessages(),
|
||||
errors: errors.errorMessages(),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -202,12 +549,12 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
|
||||
SessionData.upsert({ userId: this.userId, sessionId }, update);
|
||||
|
||||
const boards = [];
|
||||
const swimlanes = [];
|
||||
const lists = [];
|
||||
const users = [this.userId];
|
||||
|
||||
if (cards) {
|
||||
const boards = [];
|
||||
const swimlanes = [];
|
||||
const lists = [];
|
||||
const users = [this.userId];
|
||||
|
||||
cards.forEach(card => {
|
||||
if (card.boardId) boards.push(card.boardId);
|
||||
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
|
||||
|
|
@ -223,28 +570,24 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
title: 1,
|
||||
archived: 1,
|
||||
};
|
||||
|
||||
return [
|
||||
cards,
|
||||
Boards.find({ _id: { $in: boards } }, { fields }),
|
||||
Swimlanes.find({ _id: { $in: swimlanes } }, { fields }),
|
||||
Lists.find({ _id: { $in: lists } }, { fields }),
|
||||
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
||||
SessionData.find({ userId: this.userId, sessionId }),
|
||||
];
|
||||
}
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
title: 1,
|
||||
archived: 1,
|
||||
};
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('users:', users);
|
||||
const cursors = [
|
||||
Boards.find({ _id: { $in: boards } }, { fields }),
|
||||
Swimlanes.find({ _id: { $in: swimlanes } }, { fields }),
|
||||
Lists.find({ _id: { $in: lists } }, { fields }),
|
||||
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
||||
SessionData.find({ userId: this.userId, sessionId }),
|
||||
];
|
||||
|
||||
if (cards) {
|
||||
cursors.push(cards);
|
||||
}
|
||||
|
||||
return cursors;
|
||||
return [SessionData.find({ userId: this.userId, sessionId })];
|
||||
});
|
||||
|
||||
Meteor.publish('brokenCards', function() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue