Fix Regression - due date taking a while to load all cards v8.06.

Thanks to xet7 !

Fixes #5955
This commit is contained in:
Lauri Ojansivu 2025-10-21 15:08:50 +03:00
parent 07ce151508
commit 347fa9e5cd
4 changed files with 128 additions and 143 deletions

View file

@ -246,57 +246,30 @@ class CardDueDate extends CardDate {
const theDate = this.date.get(); const theDate = this.date.get();
const now = this.now.get(); const now = this.now.get();
// Debug logging for due date classes
if (process.env.DEBUG === 'true') {
console.log(`CardDueDate classes() - Card: "${this.data().title}", Due: ${theDate}, Now: ${now}, End: ${endAt}`);
}
// If there's an end date and it's before the due date, task is completed early // If there's an end date and it's before the due date, task is completed early
if (endAt && isBefore(endAt, theDate)) { if (endAt && isBefore(endAt, theDate)) {
classes += 'completed-early'; classes += 'completed-early';
if (process.env.DEBUG === 'true') {
console.log(` -> completed-early (end date ${endAt} is before due date ${theDate})`);
}
} }
// If there's an end date, don't show due date status since task is completed // If there's an end date, don't show due date status since task is completed
else if (endAt) { else if (endAt) {
classes += 'completed'; classes += 'completed';
if (process.env.DEBUG === 'true') {
console.log(` -> completed (has end date ${endAt})`);
}
} }
// Due date logic based on current time // Due date logic based on current time
else { else {
const daysDiff = diff(theDate, now, 'days'); const daysDiff = diff(theDate, now, 'days');
if (process.env.DEBUG === 'true') {
console.log(` -> daysDiff: ${daysDiff} (due: ${theDate}, now: ${now})`);
}
if (daysDiff < 0) { if (daysDiff < 0) {
// Due date is in the past - overdue // Due date is in the past - overdue
classes += 'overdue'; classes += 'overdue';
if (process.env.DEBUG === 'true') {
console.log(` -> overdue (${Math.abs(daysDiff)} days past due)`);
}
} else if (daysDiff <= 1) { } else if (daysDiff <= 1) {
// Due today or tomorrow - due soon // Due today or tomorrow - due soon
classes += 'due-soon'; classes += 'due-soon';
if (process.env.DEBUG === 'true') {
console.log(` -> due-soon (due in ${daysDiff} days)`);
}
} else { } else {
// Due date is more than 1 day away - not due yet // Due date is more than 1 day away - not due yet
classes += 'not-due'; classes += 'not-due';
if (process.env.DEBUG === 'true') {
console.log(` -> not-due (due in ${daysDiff} days)`);
}
} }
} }
if (process.env.DEBUG === 'true') {
console.log(` -> Final classes: "${classes}"`);
}
return classes; return classes;
} }

View file

@ -1,13 +1,5 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { CardSearchPagedComponent } from '../../lib/cardSearch'; import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
import {
OPERATOR_HAS,
OPERATOR_SORT,
OPERATOR_USER,
ORDER_ASCENDING,
PREDICATE_DUE_AT,
} from '../../../config/search-const';
import { QueryParams } from '../../../config/query-classes';
// const subManager = new SubsManager(); // const subManager = new SubsManager();
@ -38,6 +30,23 @@ Template.dueCards.helpers({
} }
return []; return [];
}, },
hasResults() {
const component = BlazeComponent.getComponentForElement(this);
if (component && component.dueCardsList) {
const cards = component.dueCardsList();
return cards && cards.length > 0;
}
return false;
},
searching() {
return false; // No longer using search, so always false
},
hasQueryErrors() {
return false; // No longer using search, so always false
},
errorMessages() {
return []; // No longer using search, so always empty
},
}); });
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
@ -62,71 +71,29 @@ BlazeComponent.extendComponent({
}, },
}).register('dueCardsViewChangePopup'); }).register('dueCardsViewChangePopup');
class DueCardsComponent extends CardSearchPagedComponent { class DueCardsComponent extends BlazeComponent {
onCreated() { onCreated() {
super.onCreated(); super.onCreated();
// Add a small delay to ensure ReactiveCache is ready this._cachedCards = null;
this.searchRetryCount = 0; this._cachedTimestamp = null;
this.maxRetries = 3; this.subscriptionHandle = null;
// Use a timeout to ensure the search runs after the component is fully initialized // Subscribe to the optimized due cards publication
Meteor.setTimeout(() => { this.autorun(() => {
this.performSearch(); const allUsers = this.dueCardsView() === 'all';
}, 100); if (this.subscriptionHandle) {
} this.subscriptionHandle.stop();
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.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
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,
exists: true,
});
// queryParams[OPERATOR_LIMIT] = 5;
queryParams.addPredicate(OPERATOR_SORT, {
name: PREDICATE_DUE_AT,
order: ORDER_ASCENDING,
}); });
}
// Note: User filtering is handled server-side based on board membership onDestroyed() {
// The OPERATOR_USER filter is too restrictive as it only shows cards where super.onDestroyed();
// the user is assigned or a member of the card, not the board if (this.subscriptionHandle) {
// if (Utils && Utils.dueCardsView && Utils.dueCardsView() !== 'all') { this.subscriptionHandle.stop();
// 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() { dueCardsView() {
@ -140,36 +107,36 @@ class DueCardsComponent extends CardSearchPagedComponent {
} }
dueCardsList() { dueCardsList() {
const results = this.getResults(); // Use cached results if available to avoid expensive re-sorting
console.log('results:', results); if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
const cards = []; return this._cachedCards;
if (results) { }
results.forEach(card => {
cards.push(card); // Get cards directly from the subscription (already sorted by the publication)
const cards = ReactiveCache.getCards({
type: 'cardType-card',
archived: false,
dueAt: { $exists: true, $nin: [null, ''] }
});
// Filter cards based on user view preference
const allUsers = this.dueCardsView() === 'all';
const currentUser = ReactiveCache.getCurrentUser();
let filteredCards = cards;
if (!allUsers && currentUser) {
filteredCards = cards.filter(card => {
return card.members && card.members.includes(currentUser._id) ||
card.assignees && card.assignees.includes(currentUser._id) ||
card.userId === currentUser._id;
}); });
} }
// Sort by due date: oldest first (ascending order) // Cache the results for 5 seconds to avoid re-filtering on every render
cards.sort((a, b) => { this._cachedCards = filteredCards;
// Handle null/undefined due dates by putting them at the end this._cachedTimestamp = Date.now();
const aDueAt = a.dueAt ? new Date(a.dueAt) : new Date('2100-12-31');
const bDueAt = b.dueAt ? new Date(b.dueAt) : new Date('2100-12-31');
// Debug logging return filteredCards;
if (process.env.DEBUG === 'true') {
console.log(`Comparing cards: "${a.title}" (${a.dueAt}) vs "${b.title}" (${b.dueAt})`);
console.log(`Parsed dates: ${aDueAt.toISOString()} vs ${bDueAt.toISOString()}`);
console.log(`Time difference: ${aDueAt.getTime() - bDueAt.getTime()}`);
}
// Compare dates: if a is earlier than b, return negative (a comes first)
// if a is later than b, return positive (b comes first)
return aDueAt.getTime() - bDueAt.getTime();
});
// eslint-disable-next-line no-console
console.log('cards sorted by due date (oldest first):', cards);
return cards;
} }
} }

View file

@ -179,11 +179,16 @@ export class CardSearchPagedComponent extends BlazeComponent {
console.log('getResults - no sessionData or no cards array, trying direct card search'); console.log('getResults - no sessionData or no cards array, trying direct card search');
} }
// Fallback: try to get cards directly from the client-side collection // Fallback: try to get cards directly from the client-side collection
// Use a more efficient query with limit and sort
const selector = { const selector = {
type: 'cardType-card', type: 'cardType-card',
dueAt: { $exists: true, $nin: [null, ''] } dueAt: { $exists: true, $nin: [null, ''] }
}; };
const allCards = Cards.find(selector).fetch(); const options = {
sort: { dueAt: 1 }, // Sort by due date ascending (oldest first)
limit: 100 // Limit to 100 cards for performance
};
const allCards = Cards.find(selector, options).fetch();
if (process.env.DEBUG === 'true') { if (process.env.DEBUG === 'true') {
console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards'); console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards');
} }
@ -191,9 +196,6 @@ export class CardSearchPagedComponent extends BlazeComponent {
if (allCards && allCards.length > 0) { if (allCards && allCards.length > 0) {
allCards.forEach(card => { allCards.forEach(card => {
if (card && card._id) { if (card && card._id) {
if (process.env.DEBUG === 'true') {
console.log('getResults - direct card:', card._id, card.title);
}
cards.push(card); cards.push(card);
} }
}); });

View file

@ -121,26 +121,69 @@ Meteor.publish('myCards', function(sessionId) {
return ret; return ret;
}); });
// Meteor.publish('dueCards', function(sessionId, allUsers = false) { // Optimized due cards publication for better performance
// check(sessionId, String); Meteor.publish('dueCards', function(allUsers = false) {
// check(allUsers, Boolean); check(allUsers, Boolean);
//
// // eslint-disable-next-line no-console const userId = this.userId;
// // console.log('all users:', allUsers); if (!userId) {
// return this.ready();
// const queryParams = { }
// has: [{ field: 'dueAt', exists: true }],
// limit: 25, if (process.env.DEBUG === 'true') {
// skip: 0, console.log('dueCards publication called for user:', userId, 'allUsers:', allUsers);
// sort: { name: 'dueAt', order: 'des' }, }
// };
// // Get user's board memberships for efficient filtering
// if (!allUsers) { const userBoards = ReactiveCache.getBoards({
// queryParams.users = [ReactiveCache.getCurrentUser().username]; members: userId
// } }).map(board => board._id);
//
// return buildQuery(sessionId, queryParams); if (userBoards.length === 0) {
// }); return this.ready();
}
// Build optimized selector
const selector = {
type: 'cardType-card',
archived: false,
dueAt: { $exists: true, $nin: [null, ''] },
boardId: { $in: userBoards }
};
// Add user filtering if not showing all users
if (!allUsers) {
selector.$or = [
{ members: userId },
{ assignees: userId },
{ userId: userId }
];
}
const options = {
sort: { dueAt: 1 }, // Sort by due date ascending (oldest first)
limit: 100, // Limit results for performance
fields: {
title: 1,
dueAt: 1,
boardId: 1,
listId: 1,
swimlaneId: 1,
members: 1,
assignees: 1,
userId: 1,
archived: 1,
type: 1
}
};
if (process.env.DEBUG === 'true') {
console.log('dueCards selector:', JSON.stringify(selector, null, 2));
console.log('dueCards options:', JSON.stringify(options, null, 2));
}
return Cards.find(selector, options);
});
Meteor.publish('globalSearch', function(sessionId, params, text) { Meteor.publish('globalSearch', function(sessionId, params, text) {
check(sessionId, String); check(sessionId, String);