Fix sort operator

* Add server publications for next and previous page
* Add ability to sort ascending or descending
This commit is contained in:
John R. Supplee 2021-02-25 18:38:51 +02:00
parent 250e79f53c
commit 43f40c4085
5 changed files with 239 additions and 174 deletions

View file

@ -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()) {

View file

@ -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

View file

@ -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
View file

@ -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"

View file

@ -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 })];
}