From 66b444e2b0c9b2ed5f98cd1ff0cd9222b2d0c624 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sun, 19 Oct 2025 20:05:36 +0300 Subject: [PATCH] Fix unable to see My Due Cards. Thanks to xet7 ! Fixes #5948 --- client/components/cards/cardDate.js | 6 +- client/components/main/dueCards.js | 65 +++++++-- client/lib/cardSearch.js | 216 +++++++++++++++++++++++----- server/publications/cards.js | 98 ++++++++++--- 4 files changed, 321 insertions(+), 64 deletions(-) diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 685504021..8b87b0cf9 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -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'; diff --git a/client/components/main/dueCards.js b/client/components/main/dueCards.js index da113c07a..9909b5f97 100644 --- a/client/components/main/dueCards.js +++ b/client/components/main/dueCards.js @@ -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() { diff --git a/client/lib/cardSearch.js b/client/lib/cardSearch.js index 4803f815b..4b9c14ccd 100644 --- a/client/lib/cardSearch.js +++ b/client/lib/cardSearch.js @@ -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) { diff --git a/server/publications/cards.js b/server/publications/cards.js index 8db1a542b..a47ec5f63 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -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]; }