Fix unable to see My Due Cards.

Thanks to xet7 !

Fixes #5948
This commit is contained in:
Lauri Ojansivu 2025-10-19 20:05:36 +03:00
parent 23860b1ee8
commit 66b444e2b0
4 changed files with 321 additions and 64 deletions

View file

@ -160,9 +160,9 @@ class CardReceivedDate extends CardDate {
const theDate = this.date.get();
// if dueAt, endAt and startAt exist & are > receivedAt, receivedAt doesn't need to be flagged
if (
(startAt && theDate.isAfter(startAt)) ||
(endAt && theDate.isAfter(endAt)) ||
(dueAt && theDate.isAfter(dueAt))
(startAt && isAfter(theDate, startAt)) ||
(endAt && isAfter(theDate, endAt)) ||
(dueAt && isAfter(theDate, dueAt))
)
classes += 'long-overdue';
else classes += 'current';

View file

@ -15,7 +15,7 @@ BlazeComponent.extendComponent({
dueCardsView() {
// eslint-disable-next-line no-console
// console.log('sort:', Utils.dueCardsView());
return Utils.dueCardsView();
return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me';
},
events() {
@ -38,12 +38,16 @@ BlazeComponent.extendComponent({
return [
{
'click .js-due-cards-view-me'() {
Utils.setDueCardsView('me');
if (Utils && Utils.setDueCardsView) {
Utils.setDueCardsView('me');
}
Popup.back();
},
'click .js-due-cards-view-all'() {
Utils.setDueCardsView('all');
if (Utils && Utils.setDueCardsView) {
Utils.setDueCardsView('all');
}
Popup.back();
},
},
@ -54,7 +58,38 @@ BlazeComponent.extendComponent({
class DueCardsComponent extends CardSearchPagedComponent {
onCreated() {
super.onCreated();
// Add a small delay to ensure ReactiveCache is ready
this.searchRetryCount = 0;
this.maxRetries = 3;
// Use a timeout to ensure the search runs after the component is fully initialized
Meteor.setTimeout(() => {
this.performSearch();
}, 100);
}
performSearch() {
if (process.env.DEBUG === 'true') {
console.log('Performing due cards search, attempt:', this.searchRetryCount + 1);
}
// Check if user is authenticated
const currentUser = ReactiveCache.getCurrentUser();
if (!currentUser) {
if (process.env.DEBUG === 'true') {
console.log('User not authenticated, waiting...');
}
Meteor.setTimeout(() => {
this.performSearch();
}, 1000);
return;
}
if (process.env.DEBUG === 'true') {
console.log('User authenticated:', currentUser.username);
}
const queryParams = new QueryParams();
queryParams.addPredicate(OPERATOR_HAS, {
field: PREDICATE_DUE_AT,
@ -66,17 +101,31 @@ class DueCardsComponent extends CardSearchPagedComponent {
order: ORDER_ASCENDING,
});
if (Utils.dueCardsView() !== 'all') {
queryParams.addPredicate(OPERATOR_USER, ReactiveCache.getCurrentUser().username);
}
// Note: User filtering is handled server-side based on board membership
// The OPERATOR_USER filter is too restrictive as it only shows cards where
// the user is assigned or a member of the card, not the board
// if (Utils && Utils.dueCardsView && Utils.dueCardsView() !== 'all') {
// const currentUser = ReactiveCache.getCurrentUser();
// if (currentUser && currentUser.username) {
// queryParams.addPredicate(OPERATOR_USER, currentUser.username);
// }
// }
// Debug: Log the query parameters
if (process.env.DEBUG === 'true') {
console.log('Due cards query params:', queryParams.params);
console.log('Due cards query text:', queryParams.text);
console.log('Due cards has predicates:', queryParams.getPredicates('has'));
console.log('Due cards sort predicates:', queryParams.getPredicates('sort'));
}
this.runGlobalSearch(queryParams);
}
dueCardsView() {
// eslint-disable-next-line no-console
//console.log('sort:', Utils.dueCardsView());
return Utils.dueCardsView();
return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me';
}
sortByBoard() {

View file

@ -29,21 +29,86 @@ export class CardSearchPagedComponent extends BlazeComponent {
const that = this;
this.subscriptionCallbacks = {
onReady() {
that.getResults();
that.searching.set(false);
that.hasResults.set(true);
that.serverError.set(false);
if (process.env.DEBUG === 'true') {
console.log('Subscription ready, getting results...');
console.log('Subscription ready - sessionId:', that.sessionId);
}
// Wait for session data to be available (with timeout)
let waitCount = 0;
const maxWaitCount = 50; // 10 seconds max wait
const waitForSessionData = () => {
waitCount++;
const sessionData = that.getSessionData();
if (process.env.DEBUG === 'true') {
console.log('waitForSessionData - attempt', waitCount, 'session data:', sessionData);
}
if (sessionData) {
const results = that.getResults();
if (process.env.DEBUG === 'true') {
console.log('Search results count:', results ? results.length : 0);
}
// If no results and this is a due cards search, try to retry
if ((!results || results.length === 0) && that.searchRetryCount !== undefined && that.searchRetryCount < that.maxRetries) {
if (process.env.DEBUG === 'true') {
console.log('No results found, retrying search...');
}
that.searchRetryCount++;
Meteor.setTimeout(() => {
if (that.performSearch) {
that.performSearch();
}
}, 500);
return;
}
that.searching.set(false);
that.hasResults.set(true);
that.serverError.set(false);
} else if (waitCount < maxWaitCount) {
// Session data not available yet, wait a bit more
if (process.env.DEBUG === 'true') {
console.log('Session data not available yet, waiting... (attempt', waitCount, 'of', maxWaitCount, ')');
}
Meteor.setTimeout(waitForSessionData, 200);
} else {
// Timeout reached, try fallback search
if (process.env.DEBUG === 'true') {
console.log('Timeout reached waiting for session data, trying fallback search');
}
const results = that.getResults();
if (process.env.DEBUG === 'true') {
console.log('Fallback search results count:', results ? results.length : 0);
}
if (results && results.length > 0) {
that.searching.set(false);
that.hasResults.set(true);
that.serverError.set(false);
} else {
that.searching.set(false);
that.hasResults.set(false);
that.serverError.set(true);
}
}
};
// Start waiting for session data
Meteor.setTimeout(waitForSessionData, 100);
},
onError(error) {
if (process.env.DEBUG === 'true') {
console.log('Subscription error:', error);
console.log('Error.reason:', error.reason);
console.log('Error.message:', error.message);
console.log('Error.stack:', error.stack);
}
that.searching.set(false);
that.hasResults.set(false);
that.serverError.set(true);
// eslint-disable-next-line no-console
//console.log('Error.reason:', error.reason);
// eslint-disable-next-line no-console
//console.log('Error.message:', error.message);
// eslint-disable-next-line no-console
//console.log('Error.stack:', error.stack);
},
};
}
@ -62,9 +127,28 @@ export class CardSearchPagedComponent extends BlazeComponent {
}
getSessionData(sessionId) {
return ReactiveCache.getSessionData({
sessionId: sessionId || SessionData.getSessionId(),
const sessionIdToUse = sessionId || SessionData.getSessionId();
if (process.env.DEBUG === 'true') {
console.log('getSessionData - looking for sessionId:', sessionIdToUse);
}
// Try using the raw SessionData collection instead of ReactiveCache
const sessionData = SessionData.findOne({
sessionId: sessionIdToUse,
});
if (process.env.DEBUG === 'true') {
console.log('getSessionData - found session data (raw):', sessionData);
}
// Also try ReactiveCache for comparison
const reactiveSessionData = ReactiveCache.getSessionData({
sessionId: sessionIdToUse,
});
if (process.env.DEBUG === 'true') {
console.log('getSessionData - found session data (reactive):', reactiveSessionData);
}
return sessionData || reactiveSessionData;
}
getResults() {
@ -72,33 +156,85 @@ export class CardSearchPagedComponent extends BlazeComponent {
// console.log('getting results');
this.sessionData = this.getSessionData();
// eslint-disable-next-line no-console
console.log('session data:', this.sessionData);
if (process.env.DEBUG === 'true') {
console.log('getResults - sessionId:', this.sessionId);
console.log('getResults - session data:', this.sessionData);
}
const cards = [];
this.sessionData.cards.forEach(cardId => {
cards.push(ReactiveCache.getCard(cardId));
});
this.queryErrors = this.sessionData.errors;
if (this.sessionData && this.sessionData.cards) {
if (process.env.DEBUG === 'true') {
console.log('getResults - cards array length:', this.sessionData.cards.length);
}
this.sessionData.cards.forEach(cardId => {
const card = ReactiveCache.getCard(cardId);
if (process.env.DEBUG === 'true') {
console.log('getResults - card:', cardId, card);
}
cards.push(card);
});
this.queryErrors = this.sessionData.errors || [];
} else {
if (process.env.DEBUG === 'true') {
console.log('getResults - no sessionData or no cards array, trying direct card search');
}
// Fallback: try to get cards directly from the client-side collection
const selector = {
type: 'cardType-card',
dueAt: { $exists: true, $nin: [null, ''] }
};
const allCards = Cards.find(selector).fetch();
if (process.env.DEBUG === 'true') {
console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards');
}
if (allCards && allCards.length > 0) {
allCards.forEach(card => {
if (card && card._id) {
if (process.env.DEBUG === 'true') {
console.log('getResults - direct card:', card._id, card.title);
}
cards.push(card);
}
});
}
this.queryErrors = [];
}
if (this.queryErrors.length) {
// console.log('queryErrors:', this.queryErrorMessages());
this.hasQueryErrors.set(true);
// return null;
}
this.debug.set(new QueryDebug(this.sessionData.debug));
console.log('debug:', this.debug.get().get());
console.log('debug.show():', this.debug.get().show());
console.log('debug.showSelector():', this.debug.get().showSelector());
this.debug.set(new QueryDebug(this.sessionData ? this.sessionData.debug : null));
if (process.env.DEBUG === 'true') {
console.log('debug:', this.debug.get().get());
console.log('debug.show():', this.debug.get().show());
console.log('debug.showSelector():', this.debug.get().showSelector());
}
if (cards) {
this.totalHits = this.sessionData.totalHits;
this.resultsCount = cards.length;
this.resultsStart = this.sessionData.lastHit - this.resultsCount + 1;
this.resultsEnd = this.sessionData.lastHit;
this.resultsHeading.set(this.getResultsHeading());
this.results.set(cards);
this.hasNextPage.set(this.sessionData.lastHit < this.sessionData.totalHits);
this.hasPreviousPage.set(
this.sessionData.lastHit - this.sessionData.resultsCount > 0,
);
if (this.sessionData) {
this.totalHits = this.sessionData.totalHits || 0;
this.resultsCount = cards.length;
this.resultsStart = this.sessionData.lastHit - this.resultsCount + 1;
this.resultsEnd = this.sessionData.lastHit;
this.resultsHeading.set(this.getResultsHeading());
this.results.set(cards);
this.hasNextPage.set(this.sessionData.lastHit < this.sessionData.totalHits);
this.hasPreviousPage.set(
this.sessionData.lastHit - this.sessionData.resultsCount > 0,
);
} else {
this.totalHits = cards.length;
this.resultsCount = cards.length;
this.resultsStart = 1;
this.resultsEnd = cards.length;
this.resultsHeading.set(this.getResultsHeading());
this.results.set(cards);
this.hasNextPage.set(false);
this.hasPreviousPage.set(false);
}
return cards;
}
@ -113,13 +249,29 @@ export class CardSearchPagedComponent extends BlazeComponent {
}
getSubscription(queryParams) {
return Meteor.subscribe(
if (process.env.DEBUG === 'true') {
console.log('Subscribing to globalSearch with:', {
sessionId: this.sessionId,
params: queryParams.params,
text: queryParams.text
});
}
// Subscribe to both globalSearch and sessionData
const globalSearchHandle = Meteor.subscribe(
'globalSearch',
this.sessionId,
queryParams.params,
queryParams.text,
this.subscriptionCallbacks,
);
const sessionDataHandle = Meteor.subscribe('sessionData', this.sessionId);
if (process.env.DEBUG === 'true') {
console.log('Subscribed to sessionData with sessionId:', this.sessionId);
}
return globalSearchHandle;
}
runGlobalSearch(queryParams) {

View file

@ -147,13 +147,31 @@ Meteor.publish('globalSearch', function(sessionId, params, text) {
check(params, Object);
check(text, String);
// eslint-disable-next-line no-console
// console.log('queryParams:', params);
if (process.env.DEBUG === 'true') {
console.log('globalSearch publication called with:', { sessionId, params, text });
}
const ret = findCards(sessionId, buildQuery(new QueryParams(params, text)));
if (process.env.DEBUG === 'true') {
console.log('globalSearch publication returning:', ret);
}
return ret;
});
Meteor.publish('sessionData', function(sessionId) {
check(sessionId, String);
const userId = Meteor.userId();
if (process.env.DEBUG === 'true') {
console.log('sessionData publication called with:', { sessionId, userId });
}
const cursor = SessionData.find({ userId, sessionId });
if (process.env.DEBUG === 'true') {
console.log('sessionData publication returning cursor with count:', cursor.count());
}
return cursor;
});
function buildSelector(queryParams) {
const userId = Meteor.userId();
@ -261,8 +279,12 @@ function buildSelector(queryParams) {
selector.archived = false;
}
} else {
const userBoardIds = Boards.userBoardIds(userId, null, boardsSelector);
if (process.env.DEBUG === 'true') {
console.log('buildSelector - userBoardIds:', userBoardIds);
}
selector.boardId = {
$in: Boards.userBoardIds(userId, null, boardsSelector),
$in: userBoardIds,
};
}
if (endAt !== null) {
@ -537,8 +559,9 @@ function buildSelector(queryParams) {
}
}
// eslint-disable-next-line no-console
// console.log('cards selector:', JSON.stringify(selector, null, 2));
if (process.env.DEBUG === 'true') {
console.log('buildSelector - final selector:', JSON.stringify(selector, null, 2));
}
const query = new Query();
query.selector = selector;
@ -702,14 +725,17 @@ function findCards(sessionId, query) {
const userId = Meteor.userId();
// eslint-disable-next-line no-console
// console.log('selector:', query.selector);
// console.log('selector.$and:', query.selector.$and);
// eslint-disable-next-line no-console
// console.log('projection:', query.projection);
if (process.env.DEBUG === 'true') {
console.log('findCards - userId:', userId);
console.log('findCards - selector:', JSON.stringify(query.selector, null, 2));
console.log('findCards - selector.$and:', query.selector.$and);
console.log('findCards - projection:', query.projection);
}
const cards = ReactiveCache.getCards(query.selector, query.projection, true);
// eslint-disable-next-line no-console
// console.log('count:', cards.count());
if (process.env.DEBUG === 'true') {
console.log('findCards - cards count:', cards ? cards.count() : 0);
}
const update = {
$set: {
@ -720,7 +746,8 @@ function findCards(sessionId, query) {
selector: SessionData.pickle(query.selector),
projection: SessionData.pickle(query.projection),
errors: query.errors(),
debug: query.getQueryParams().getPredicate(OPERATOR_DEBUG)
debug: query.getQueryParams().getPredicate(OPERATOR_DEBUG),
modifiedAt: new Date()
},
};
@ -736,13 +763,22 @@ function findCards(sessionId, query) {
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);
if (process.env.DEBUG === 'true') {
console.log('findCards - sessionId:', sessionId);
console.log('findCards - userId:', userId);
console.log('findCards - update:', JSON.stringify(update, null, 2));
}
const upsertResult = SessionData.upsert({ userId, sessionId }, update);
if (process.env.DEBUG === 'true') {
console.log('findCards - upsertResult:', upsertResult);
}
// Check if the session data was actually stored
const storedSessionData = SessionData.findOne({ userId, sessionId });
if (process.env.DEBUG === 'true') {
console.log('findCards - stored session data:', storedSessionData);
console.log('findCards - stored session data count:', storedSessionData ? 1 : 0);
}
// remove old session data
SessionData.remove({
@ -793,6 +829,21 @@ function findCards(sessionId, query) {
type: 1,
};
// Add a small delay to ensure the session data is committed to the database
Meteor.setTimeout(() => {
const sessionDataCursor = SessionData.find({ userId, sessionId });
if (process.env.DEBUG === 'true') {
console.log('findCards - publishing session data cursor (after delay):', sessionDataCursor);
console.log('findCards - session data count (after delay):', sessionDataCursor.count());
}
}, 100);
const sessionDataCursor = SessionData.find({ userId, sessionId });
if (process.env.DEBUG === 'true') {
console.log('findCards - publishing session data cursor:', sessionDataCursor);
console.log('findCards - session data count:', sessionDataCursor.count());
}
return [
cards,
ReactiveCache.getBoards(
@ -812,9 +863,14 @@ function findCards(sessionId, query) {
ReactiveCache.getChecklistItems({ cardId: { $in: cards.map(c => c._id) } }, {}, true),
ReactiveCache.getAttachments({ 'meta.cardId': { $in: cards.map(c => c._id) } }, {}, true).cursor,
ReactiveCache.getCardComments({ cardId: { $in: cards.map(c => c._id) } }, {}, true),
SessionData.find({ userId, sessionId }),
sessionDataCursor,
];
}
return [SessionData.find({ userId, sessionId })];
const sessionDataCursor = SessionData.find({ userId, sessionId });
if (process.env.DEBUG === 'true') {
console.log('findCards - publishing session data cursor (no cards):', sessionDataCursor);
console.log('findCards - session data count (no cards):', sessionDataCursor.count());
}
return [sessionDataCursor];
}