Bug fix for issue #3698

* Rewrite routine for building the My Cards hierarchical list
* Use a separate publication for retrieving My Cards
* Fix bug with limit and skip projectsion
This commit is contained in:
John R. Supplee 2021-04-12 17:34:02 +02:00
parent 769bb7a55d
commit 07a3301414
5 changed files with 104 additions and 176 deletions

View file

@ -6,16 +6,6 @@ template(name="myCardsHeaderBar")
i.fa.fa-list i.fa.fa-list
| {{_ 'my-cards'}} | {{_ 'my-cards'}}
.board-header-btns.left
a.board-header-btn.js-toggle-my-cards-choose-sort(title="{{_ 'myCardsSortChange-title'}}")
i.fa.fa-caret-down
if $eq myCardsSort 'board'
i.fa.fa-th-large
| {{_ 'myCardsSortChange-choice-board'}}
if $eq myCardsSort 'dueAt'
i.fa.fa-calendar
| {{_ 'myCardsSortChange-choice-dueat'}}
template(name="myCardsModalTitle") template(name="myCardsModalTitle")
if currentUser if currentUser
h2 h2
@ -28,46 +18,22 @@ template(name="myCards")
+spinner +spinner
else else
.wrapper .wrapper
if $eq myCardsSort 'board' each board in myCardsList
each board in myCardsList .my-cards-board-wrapper
.my-cards-board-wrapper .my-cards-board-title(class=board.colorClass, id="header")
.my-cards-board-title(class=board.colorClass, id="header") a(href=board.originRelativeUrl)
a(href=board.originRelativeUrl) +viewer
+viewer = board.title
= board.title each swimlane in board.mySwimlanes
each swimlane in board.mySwimlanes .my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}") +viewer
+viewer = swimlane.title
= swimlane.title each list in swimlane.myLists
each list in swimlane.myLists .my-cards-list-wrapper
.my-cards-list-wrapper .my-cards-list-title(class=list.colorClass)
.my-cards-list-title(class=list.colorClass) +viewer
+viewer = list.title
= list.title each card in list.myCards
each card in list.myCards .my-cards-card-wrapper
.my-cards-card-wrapper a.minicard-wrapper(href=card.originRelativeUrl)
a.minicard-wrapper(href=card.originRelativeUrl) +minicard(card)
+minicard(card)
else
.my-cards-dueat-list-wrapper
each card in myDueCardsList
+resultCard(card)
template(name="myCardsSortChangePopup")
if currentUser
ul.pop-over-list
li
with "my-cards-sort-board"
a.js-my-cards-sort-board
i.fa.fa-th-large.colorful
| {{_ 'myCardsSortChange-choice-board'}}
if $eq Utils.myCardsSort "board"
i.fa.fa-check
hr
li
with "my-cards-sort-dueat"
a.js-my-cards-sort-dueat
i.fa.fa-calendar.colorful
| {{_ 'myCardsSortChange-choice-dueat'}}
if $eq Utils.myCardsSort "dueAt"
i.fa.fa-check

View file

@ -1,14 +1,4 @@
import { CardSearchPagedComponent } from '../../lib/cardSearch'; import { CardSearchPagedComponent } from '../../lib/cardSearch';
import { QueryParams } from '../../../config/query-classes';
import {
OPERATOR_LIMIT,
OPERATOR_SORT,
OPERATOR_USER,
ORDER_DESCENDING,
PREDICATE_DUE_AT,
} from '../../../config/search-const';
// const subManager = new SubsManager();
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
myCardsSort() { myCardsSort() {
@ -34,48 +24,21 @@ Template.myCards.helpers({
}, },
}); });
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-my-cards-sort-board'() {
Utils.setMyCardsSort('board');
Popup.close();
},
'click .js-my-cards-sort-dueat'() {
Utils.setMyCardsSort('dueAt');
Popup.close();
},
},
];
},
}).register('myCardsSortChangePopup');
class MyCardsComponent extends CardSearchPagedComponent { class MyCardsComponent extends CardSearchPagedComponent {
onCreated() { onCreated() {
super.onCreated(); super.onCreated();
const queryParams = new QueryParams(); this.runGlobalSearch(null);
queryParams.addPredicate(OPERATOR_USER, Meteor.user().username);
queryParams.addPredicate(OPERATOR_SORT, {
name: PREDICATE_DUE_AT,
order: ORDER_DESCENDING,
});
queryParams.addPredicate(OPERATOR_LIMIT, 100);
this.runGlobalSearch(queryParams);
Meteor.subscribe('setting'); Meteor.subscribe('setting');
} }
myCardsSort() { // eslint-disable-next-line no-unused-vars
// eslint-disable-next-line no-console getSubscription(queryParams) {
//console.log('sort:', Utils.myCardsSort()); return Meteor.subscribe(
return Utils.myCardsSort(); 'myCards',
} this.sessionId,
this.subscriptionCallbacks,
sortByBoard() { );
return this.myCardsSort() === 'board';
} }
myCardsList() { myCardsList() {
@ -87,35 +50,9 @@ class MyCardsComponent extends CardSearchPagedComponent {
const cursor = this.getResults(); const cursor = this.getResults();
if (cursor) { if (cursor) {
let newBoard = false;
let newSwimlane = false;
let newList = false;
cursor.forEach(card => { cursor.forEach(card => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
// console.log('card:', card.title); // console.log('card:', card.title);
if (list === null || card.listId !== list._id) {
// eslint-disable-next-line no-console
// console.log('new list');
list = card.getList();
if (list.archived) {
list = null;
return;
}
list.myCards = [card];
newList = true;
}
if (swimlane === null || card.swimlaneId !== swimlane._id) {
// eslint-disable-next-line no-console
// console.log('new swimlane');
swimlane = card.getSwimlane();
if (swimlane.archived) {
swimlane = null;
return;
}
swimlane.myLists = [list];
newSwimlane = true;
}
if (board === null || card.boardId !== board._id) { if (board === null || card.boardId !== board._id) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
// console.log('new board'); // console.log('new board');
@ -126,23 +63,38 @@ class MyCardsComponent extends CardSearchPagedComponent {
} }
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
// console.log('board:', b, b._id, b.title); // console.log('board:', b, b._id, b.title);
board.mySwimlanes = [swimlane];
newBoard = true;
}
if (newBoard) {
boards.push(board); boards.push(board);
} else if (newSwimlane) { board.mySwimlanes = [];
board.mySwimlanes.push(swimlane); swimlane = null;
} else if (newList) { list = null;
swimlane.myLists.push(list);
} else {
list.myCards.push(card);
} }
newBoard = false; if (swimlane === null || card.swimlaneId !== swimlane._id) {
newSwimlane = false; // eslint-disable-next-line no-console
newList = false; // console.log('new swimlane');
swimlane = card.getSwimlane();
if (swimlane.archived) {
swimlane = null;
return;
}
board.mySwimlanes.push(swimlane);
swimlane.myLists = [];
list = null;
}
if (list === null || card.listId !== list._id) {
// eslint-disable-next-line no-console
// console.log('new list');
list = card.getList();
if (list.archived) {
list = null;
return;
}
swimlane.myLists.push(list);
list.myCards = [];
}
list.myCards.push(card);
}); });
// sort the data structure // sort the data structure
@ -182,27 +134,5 @@ class MyCardsComponent extends CardSearchPagedComponent {
return []; return [];
} }
myDueCardsList() {
const cursor = this.getResults();
const cards = [];
cursor.forEach(card => {
cards.push(card);
});
cards.sort((a, b) => {
const x = a.dueAt === null ? new Date('2100-12-31') : a.dueAt;
const y = b.dueAt === null ? new Date('2100-12-31') : b.dueAt;
if (x > y) return 1;
else if (x < y) return -1;
return 0;
});
// eslint-disable-next-line no-console
// console.log('cursor:', cards);
return cards;
}
} }
MyCardsComponent.register('myCards'); MyCardsComponent.register('myCards');

View file

@ -62,7 +62,6 @@ export class CardSearchPagedComponent extends BlazeComponent {
// console.log('getting results'); // console.log('getting results');
const sessionData = this.getSessionData(); const sessionData = this.getSessionData();
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
// console.log('selector:', sessionData.getSelector());
console.log('session data:', sessionData); console.log('session data:', sessionData);
const cards = []; const cards = [];
sessionData.cards.forEach(cardId => { sessionData.cards.forEach(cardId => {
@ -99,10 +98,8 @@ export class CardSearchPagedComponent extends BlazeComponent {
} }
} }
runGlobalSearch(queryParams) { getSubscription(queryParams) {
this.searching.set(true); return Meteor.subscribe(
this.stopSubscription();
this.subscriptionHandle = Meteor.subscribe(
'globalSearch', 'globalSearch',
this.sessionId, this.sessionId,
queryParams.params, queryParams.params,
@ -111,6 +108,12 @@ export class CardSearchPagedComponent extends BlazeComponent {
); );
} }
runGlobalSearch(queryParams) {
this.searching.set(true);
this.stopSubscription();
this.subscriptionHandle = this.getSubscription(queryParams);
}
queryErrorMessages() { queryErrorMessages() {
const messages = []; const messages = [];

View file

@ -52,7 +52,9 @@ export class QueryParams {
hasOperator(operator) { hasOperator(operator) {
return ( return (
this.params[operator] !== undefined && this.params[operator].length > 0 this.params[operator] !== undefined &&
(this.params[operator].length === undefined ||
this.params[operator].length > 0)
); );
} }
@ -68,7 +70,11 @@ export class QueryParams {
} }
getPredicate(operator) { getPredicate(operator) {
return this.params[operator][0]; if (typeof this.params[operator] === 'object') {
return this.params[operator][0];
} else {
return this.params[operator];
}
} }
getPredicates(operator) { getPredicates(operator) {
@ -196,6 +202,10 @@ export class Query {
return this.queryParams; return this.queryParams;
} }
setQueryParams(queryParams) {
this.queryParams = queryParams;
}
addPredicate(operator, predicate) { addPredicate(operator, predicate) {
this.queryParams.addPredicate(operator, predicate); this.queryParams.addPredicate(operator, predicate);
} }

View file

@ -53,10 +53,20 @@ Meteor.publish('card', cardId => {
}); });
Meteor.publish('myCards', function(sessionId) { Meteor.publish('myCards', function(sessionId) {
check(sessionId, String);
const queryParams = new QueryParams(); const queryParams = new QueryParams();
queryParams.addPredicate(OPERATOR_USER, Meteor.user().username); queryParams.addPredicate(OPERATOR_USER, Meteor.user().username);
queryParams.setPredicate(OPERATOR_LIMIT, 200);
return findCards(sessionId, buildQuery(queryParams)); const query = buildQuery(queryParams);
query.projection.sort = {
boardId: 1,
swimlaneId: 1,
listId: 1,
};
return findCards(sessionId, query);
}); });
// Meteor.publish('dueCards', function(sessionId, allUsers = false) { // Meteor.publish('dueCards', function(sessionId, allUsers = false) {
@ -449,16 +459,18 @@ function buildSelector(queryParams) {
const query = new Query(); const query = new Query();
query.selector = selector; query.selector = selector;
query.params = queryParams; query.setQueryParams(queryParams);
query._errors = errors; query._errors = errors;
return query; return query;
} }
function buildProjection(query) { function buildProjection(query) {
// eslint-disable-next-line no-console
// console.log('query:', query);
let skip = 0; let skip = 0;
if (query.params.skip) { if (query.getQueryParams().skip) {
skip = query.params.skip; skip = query.getQueryParams().skip;
} }
let limit = DEFAULT_LIMIT; let limit = DEFAULT_LIMIT;
const configLimit = parseInt(process.env.RESULTS_PER_PAGE, 10); const configLimit = parseInt(process.env.RESULTS_PER_PAGE, 10);
@ -466,8 +478,8 @@ function buildProjection(query) {
limit = configLimit; limit = configLimit;
} }
if (query.params.hasOperator(OPERATOR_LIMIT)) { if (query.getQueryParams().hasOperator(OPERATOR_LIMIT)) {
limit = query.params.getPredicate(OPERATOR_LIMIT); limit = query.getQueryParams().getPredicate(OPERATOR_LIMIT);
} }
const projection = { const projection = {
@ -499,12 +511,13 @@ function buildProjection(query) {
limit, limit,
}; };
if (query.params.hasOperator(OPERATOR_SORT)) { if (query.getQueryParams().hasOperator(OPERATOR_SORT)) {
const order = const order =
query.params.getPredicate(OPERATOR_SORT).order === ORDER_ASCENDING query.getQueryParams().getPredicate(OPERATOR_SORT).order ===
ORDER_ASCENDING
? 1 ? 1
: -1; : -1;
switch (query.params.getPredicate(OPERATOR_SORT).name) { switch (query.getQueryParams().getPredicate(OPERATOR_SORT).name) {
case PREDICATE_DUE_AT: case PREDICATE_DUE_AT:
projection.sort = { projection.sort = {
dueAt: order, dueAt: order,
@ -633,6 +646,12 @@ function findCards(sessionId, query) {
update.$set.resultsCount = update.$set.cards.length; update.$set.resultsCount = update.$set.cards.length;
} }
// eslint-disable-next-line no-console
// console.log('sessionId:', sessionId);
// eslint-disable-next-line no-console
// console.log('userId:', userId);
// eslint-disable-next-line no-console
// console.log('update:', update);
SessionData.upsert({ userId, sessionId }, update); SessionData.upsert({ userId, sessionId }, update);
// remove old session data // remove old session data