mirror of
https://github.com/wekan/wekan.git
synced 2025-04-22 04:57:07 -04:00
Fix sort operator
* Add server publications for next and previous page * Add ability to sort ascending or descending
This commit is contained in:
parent
250e79f53c
commit
43f40c4085
5 changed files with 239 additions and 174 deletions
|
@ -116,7 +116,12 @@ BlazeComponent.extendComponent({
|
|||
// eslint-disable-next-line no-console
|
||||
// console.log('selector:', sessionData.getSelector());
|
||||
// console.log('session data:', sessionData);
|
||||
const cards = Cards.find({ _id: { $in: sessionData.cards } });
|
||||
const projection = sessionData.getProjection();
|
||||
projection.skip = 0;
|
||||
const cards = Cards.find(
|
||||
{ _id: { $in: sessionData.cards } },
|
||||
projection,
|
||||
);
|
||||
this.queryErrors = sessionData.errors;
|
||||
if (this.queryErrors.length) {
|
||||
this.hasQueryErrors.set(true);
|
||||
|
@ -201,6 +206,7 @@ BlazeComponent.extendComponent({
|
|||
'^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
|
||||
'u',
|
||||
);
|
||||
const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
|
||||
|
||||
const operators = {
|
||||
'operator-board': 'boards',
|
||||
|
@ -223,6 +229,7 @@ BlazeComponent.extendComponent({
|
|||
'operator-modified': 'modifiedAt',
|
||||
'operator-comment': 'comments',
|
||||
'operator-has': 'has',
|
||||
'operator-sort': 'sort',
|
||||
};
|
||||
|
||||
const predicates = {
|
||||
|
@ -346,13 +353,22 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
}
|
||||
} else if (operatorMap[op] === 'sort') {
|
||||
let negated = false;
|
||||
const m = value.match(reNegatedOperator);
|
||||
if (m) {
|
||||
value = m.groups.operator;
|
||||
negated = true;
|
||||
}
|
||||
if (!predicateTranslations.sorts[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-sort-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = predicateTranslations.sorts[value];
|
||||
value = {
|
||||
name: predicateTranslations.sorts[value],
|
||||
order: negated ? 'des' : 'asc',
|
||||
};
|
||||
}
|
||||
} else if (operatorMap[op] === 'status') {
|
||||
if (!predicateTranslations.status[value]) {
|
||||
|
@ -437,20 +453,10 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
nextPage() {
|
||||
sessionData = this.getSessionData();
|
||||
|
||||
const params = {
|
||||
limit: this.resultsPerPage,
|
||||
selector: sessionData.getSelector(),
|
||||
skip: sessionData.lastHit,
|
||||
};
|
||||
const sessionData = this.getSessionData();
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
SessionData.getSessionId(),
|
||||
params,
|
||||
);
|
||||
const handle = Meteor.subscribe('nextPage', sessionData.sessionId);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
|
@ -464,21 +470,10 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
previousPage() {
|
||||
sessionData = this.getSessionData();
|
||||
|
||||
const params = {
|
||||
limit: this.resultsPerPage,
|
||||
selector: sessionData.getSelector(),
|
||||
skip:
|
||||
sessionData.lastHit - sessionData.resultsCount - this.resultsPerPage,
|
||||
};
|
||||
const sessionData = this.getSessionData();
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
SessionData.getSessionId(),
|
||||
params,
|
||||
);
|
||||
const handle = Meteor.subscribe('previousPage', sessionData.sessionId);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
|
|
|
@ -117,7 +117,7 @@ CardComments.textSearch = (userId, textArray) => {
|
|||
};
|
||||
|
||||
for (const text of textArray) {
|
||||
selector.$and.push({ text: new RegExp(escapeForRegex(text)) });
|
||||
selector.$and.push({ text: new RegExp(escapeForRegex(text), 'i') });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -62,6 +62,12 @@ SessionData.attachSchema(
|
|||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
projection: {
|
||||
type: String,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
errorMessages: {
|
||||
type: [String],
|
||||
optional: true,
|
||||
|
@ -130,6 +136,9 @@ SessionData.helpers({
|
|||
getSelector() {
|
||||
return SessionData.unpickle(this.selector);
|
||||
},
|
||||
getProjection() {
|
||||
return SessionData.unpickle(this.projection);
|
||||
},
|
||||
});
|
||||
|
||||
SessionData.unpickle = pickle => {
|
||||
|
|
22
package-lock.json
generated
22
package-lock.json
generated
|
@ -718,6 +718,19 @@
|
|||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
|
||||
},
|
||||
"babel-eslint": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
||||
"integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/parser": "^7.7.0",
|
||||
"@babel/traverse": "^7.7.0",
|
||||
"@babel/types": "^7.7.0",
|
||||
"eslint-visitor-keys": "^1.0.0",
|
||||
"resolve": "^1.12.0"
|
||||
}
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||
|
@ -2384,8 +2397,7 @@
|
|||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"functional-red-black-tree": {
|
||||
"version": "1.0.1",
|
||||
|
@ -2525,7 +2537,6 @@
|
|||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
|
@ -2810,7 +2821,6 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
|
||||
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
|
@ -4941,8 +4951,7 @@
|
|||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "1.2.1",
|
||||
|
@ -5625,7 +5634,6 @@
|
|||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
|
||||
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-core-module": "^2.2.0",
|
||||
"path-parse": "^1.0.6"
|
||||
|
|
|
@ -552,6 +552,11 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
|
||||
const attachments = Attachments.find({ 'original.name': regex });
|
||||
|
||||
// const comments = CardComments.find(
|
||||
// { text: regex },
|
||||
// { fields: { cardId: 1 } },
|
||||
// );
|
||||
|
||||
selector.$and.push({
|
||||
$or: [
|
||||
{ title: regex },
|
||||
|
@ -566,6 +571,7 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
},
|
||||
{ _id: { $in: checklists.map(list => list.cardId) } },
|
||||
{ _id: { $in: attachments.map(attach => attach.cardId) } },
|
||||
// { _id: { $in: comments.map(com => com.cardId) } },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
@ -580,89 +586,206 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
// eslint-disable-next-line no-console
|
||||
// console.log('selector.$and:', selector.$and);
|
||||
|
||||
let cards = null;
|
||||
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,
|
||||
customFields: 1,
|
||||
},
|
||||
sort: {
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
},
|
||||
skip,
|
||||
limit,
|
||||
};
|
||||
|
||||
if (!errors.hasErrors()) {
|
||||
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,
|
||||
customFields: 1,
|
||||
},
|
||||
skip,
|
||||
limit,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
if (queryParams.sort) {
|
||||
const order = queryParams.sort.order === 'asc' ? 1 : -1;
|
||||
switch (queryParams.sort.name) {
|
||||
case 'dueAt':
|
||||
projection.sort = {
|
||||
dueAt: order,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
break;
|
||||
case 'modifiedAt':
|
||||
projection.sort = {
|
||||
modifiedAt: order,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
break;
|
||||
case 'createdAt':
|
||||
projection.sort = {
|
||||
createdAt: order,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
};
|
||||
break;
|
||||
case 'system':
|
||||
projection.sort = {
|
||||
boardId: order,
|
||||
swimlaneId: order,
|
||||
listId: order,
|
||||
modifiedAt: order,
|
||||
sort: order,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('projection:', projection);
|
||||
cards = Cards.find(selector, projection);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('count:', cards.count());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('projection:', projection);
|
||||
|
||||
return findCards(sessionId, selector, projection, errors);
|
||||
});
|
||||
|
||||
Meteor.publish('brokenCards', function() {
|
||||
const user = Users.findOne({ _id: this.userId });
|
||||
|
||||
const permiitedBoards = [null];
|
||||
let selector = {};
|
||||
selector.$or = [
|
||||
{ permission: 'public' },
|
||||
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
|
||||
];
|
||||
|
||||
Boards.find(selector).forEach(board => {
|
||||
permiitedBoards.push(board._id);
|
||||
});
|
||||
|
||||
selector = {
|
||||
boardId: { $in: permiitedBoards },
|
||||
$or: [
|
||||
{ boardId: { $in: [null, ''] } },
|
||||
{ swimlaneId: { $in: [null, ''] } },
|
||||
{ listId: { $in: [null, ''] } },
|
||||
],
|
||||
};
|
||||
|
||||
const cards = Cards.find(selector, {
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const boards = [];
|
||||
const swimlanes = [];
|
||||
const lists = [];
|
||||
const users = [];
|
||||
|
||||
cards.forEach(card => {
|
||||
if (card.boardId) boards.push(card.boardId);
|
||||
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
|
||||
if (card.listId) lists.push(card.listId);
|
||||
if (card.members) {
|
||||
card.members.forEach(userId => {
|
||||
users.push(userId);
|
||||
});
|
||||
}
|
||||
if (card.assignees) {
|
||||
card.assignees.forEach(userId => {
|
||||
users.push(userId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
cards,
|
||||
Boards.find({ _id: { $in: boards } }),
|
||||
Swimlanes.find({ _id: { $in: swimlanes } }),
|
||||
Lists.find({ _id: { $in: lists } }),
|
||||
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
||||
];
|
||||
});
|
||||
|
||||
Meteor.publish('nextPage', function(sessionId) {
|
||||
check(sessionId, String);
|
||||
|
||||
const session = SessionData.findOne({ sessionId });
|
||||
const projection = session.getProjection();
|
||||
projection.skip = session.lastHit;
|
||||
|
||||
return findCards(sessionId, session.getSelector(), projection);
|
||||
});
|
||||
|
||||
Meteor.publish('previousPage', function(sessionId) {
|
||||
check(sessionId, String);
|
||||
|
||||
const session = SessionData.findOne({ sessionId });
|
||||
const projection = session.getProjection();
|
||||
projection.skip = session.lastHit - session.resultsCount - projection.limit;
|
||||
|
||||
return findCards(sessionId, session.getSelector(), projection);
|
||||
});
|
||||
|
||||
function findCards(sessionId, selector, projection, errors = null) {
|
||||
// check(selector, Object);
|
||||
// check(projection, Object);
|
||||
const userId = Meteor.userId();
|
||||
|
||||
let cards;
|
||||
if (!errors || !errors.hasErrors()) {
|
||||
cards = Cards.find(selector, projection);
|
||||
}
|
||||
|
||||
console.log('selector:', selector);
|
||||
console.log('projection:', projection);
|
||||
console.log('count:', cards.count());
|
||||
const update = {
|
||||
$set: {
|
||||
totalHits: 0,
|
||||
lastHit: 0,
|
||||
resultsCount: 0,
|
||||
cards: [],
|
||||
errors: errors.errorMessages(),
|
||||
selector: SessionData.pickle(selector),
|
||||
projection: SessionData.pickle(projection),
|
||||
},
|
||||
};
|
||||
if (errors) {
|
||||
update.$set.errors = errors.errorMessages();
|
||||
}
|
||||
|
||||
if (cards) {
|
||||
update.$set.totalHits = cards.count();
|
||||
update.$set.lastHit =
|
||||
skip + limit < cards.count() ? skip + limit : cards.count();
|
||||
projection.skip + projection.limit < cards.count()
|
||||
? projection.skip + projection.limit
|
||||
: cards.count();
|
||||
update.$set.cards = cards.map(card => {
|
||||
return card._id;
|
||||
});
|
||||
|
@ -735,79 +858,9 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
Checklists.find({ cardId: { $in: cards.map(c => c._id) } }),
|
||||
Attachments.find({ cardId: { $in: cards.map(c => c._id) } }),
|
||||
CardComments.find({ cardId: { $in: cards.map(c => c._id) } }),
|
||||
SessionData.find({ userId: this.userId, sessionId }),
|
||||
SessionData.find({ userId, sessionId }),
|
||||
];
|
||||
}
|
||||
|
||||
return [SessionData.find({ userId: this.userId, sessionId })];
|
||||
});
|
||||
|
||||
Meteor.publish('brokenCards', function() {
|
||||
const user = Users.findOne({ _id: this.userId });
|
||||
|
||||
const permiitedBoards = [null];
|
||||
let selector = {};
|
||||
selector.$or = [
|
||||
{ permission: 'public' },
|
||||
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
|
||||
];
|
||||
|
||||
Boards.find(selector).forEach(board => {
|
||||
permiitedBoards.push(board._id);
|
||||
});
|
||||
|
||||
selector = {
|
||||
boardId: { $in: permiitedBoards },
|
||||
$or: [
|
||||
{ boardId: { $in: [null, ''] } },
|
||||
{ swimlaneId: { $in: [null, ''] } },
|
||||
{ listId: { $in: [null, ''] } },
|
||||
],
|
||||
};
|
||||
|
||||
const cards = Cards.find(selector, {
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const boards = [];
|
||||
const swimlanes = [];
|
||||
const lists = [];
|
||||
const users = [];
|
||||
|
||||
cards.forEach(card => {
|
||||
if (card.boardId) boards.push(card.boardId);
|
||||
if (card.swimlaneId) swimlanes.push(card.swimlaneId);
|
||||
if (card.listId) lists.push(card.listId);
|
||||
if (card.members) {
|
||||
card.members.forEach(userId => {
|
||||
users.push(userId);
|
||||
});
|
||||
}
|
||||
if (card.assignees) {
|
||||
card.assignees.forEach(userId => {
|
||||
users.push(userId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
cards,
|
||||
Boards.find({ _id: { $in: boards } }),
|
||||
Swimlanes.find({ _id: { $in: swimlanes } }),
|
||||
Lists.find({ _id: { $in: lists } }),
|
||||
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
||||
];
|
||||
});
|
||||
return [SessionData.find({ userId: userId, sessionId })];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue