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,56 +246,29 @@ class CardDueDate extends CardDate {
const theDate = this.date.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 (endAt && isBefore(endAt, theDate)) {
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
else if (endAt) {
classes += 'completed';
if (process.env.DEBUG === 'true') {
console.log(` -> completed (has end date ${endAt})`);
}
}
// Due date logic based on current time
else {
const daysDiff = diff(theDate, now, 'days');
if (process.env.DEBUG === 'true') {
console.log(` -> daysDiff: ${daysDiff} (due: ${theDate}, now: ${now})`);
}
if (daysDiff < 0) {
// Due date is in the past - overdue
classes += 'overdue';
if (process.env.DEBUG === 'true') {
console.log(` -> overdue (${Math.abs(daysDiff)} days past due)`);
}
} else if (daysDiff <= 1) {
// Due today or tomorrow - due soon
classes += 'due-soon';
if (process.env.DEBUG === 'true') {
console.log(` -> due-soon (due in ${daysDiff} days)`);
}
} else {
// Due date is more than 1 day away - not due yet
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;
}

View file

@ -1,13 +1,5 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { CardSearchPagedComponent } from '../../lib/cardSearch';
import {
OPERATOR_HAS,
OPERATOR_SORT,
OPERATOR_USER,
ORDER_ASCENDING,
PREDICATE_DUE_AT,
} from '../../../config/search-const';
import { QueryParams } from '../../../config/query-classes';
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
// const subManager = new SubsManager();
@ -38,6 +30,23 @@ Template.dueCards.helpers({
}
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({
@ -62,71 +71,29 @@ BlazeComponent.extendComponent({
},
}).register('dueCardsViewChangePopup');
class DueCardsComponent extends CardSearchPagedComponent {
class DueCardsComponent extends BlazeComponent {
onCreated() {
super.onCreated();
// Add a small delay to ensure ReactiveCache is ready
this.searchRetryCount = 0;
this.maxRetries = 3;
this._cachedCards = null;
this._cachedTimestamp = null;
this.subscriptionHandle = null;
// Use a timeout to ensure the search runs after the component is fully initialized
Meteor.setTimeout(() => {
this.performSearch();
}, 100);
// Subscribe to the optimized due cards publication
this.autorun(() => {
const allUsers = this.dueCardsView() === 'all';
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.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,
this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
});
// 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
// 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);
onDestroyed() {
super.onDestroyed();
if (this.subscriptionHandle) {
this.subscriptionHandle.stop();
}
}
dueCardsView() {
@ -140,36 +107,36 @@ class DueCardsComponent extends CardSearchPagedComponent {
}
dueCardsList() {
const results = this.getResults();
console.log('results:', results);
const cards = [];
if (results) {
results.forEach(card => {
cards.push(card);
// Use cached results if available to avoid expensive re-sorting
if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
return this._cachedCards;
}
// 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)
cards.sort((a, b) => {
// Handle null/undefined due dates by putting them at the end
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');
// Cache the results for 5 seconds to avoid re-filtering on every render
this._cachedCards = filteredCards;
this._cachedTimestamp = Date.now();
// Debug logging
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;
return filteredCards;
}
}

View file

@ -179,11 +179,16 @@ export class CardSearchPagedComponent extends BlazeComponent {
console.log('getResults - no sessionData or no cards array, trying direct card search');
}
// Fallback: try to get cards directly from the client-side collection
// Use a more efficient query with limit and sort
const selector = {
type: 'cardType-card',
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') {
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) {
allCards.forEach(card => {
if (card && card._id) {
if (process.env.DEBUG === 'true') {
console.log('getResults - direct card:', card._id, card.title);
}
cards.push(card);
}
});

View file

@ -121,26 +121,69 @@ Meteor.publish('myCards', function(sessionId) {
return ret;
});
// Meteor.publish('dueCards', function(sessionId, allUsers = false) {
// check(sessionId, String);
// check(allUsers, Boolean);
//
// // eslint-disable-next-line no-console
// // console.log('all users:', allUsers);
//
// const queryParams = {
// has: [{ field: 'dueAt', exists: true }],
// limit: 25,
// skip: 0,
// sort: { name: 'dueAt', order: 'des' },
// };
//
// if (!allUsers) {
// queryParams.users = [ReactiveCache.getCurrentUser().username];
// }
//
// return buildQuery(sessionId, queryParams);
// });
// Optimized due cards publication for better performance
Meteor.publish('dueCards', function(allUsers = false) {
check(allUsers, Boolean);
const userId = this.userId;
if (!userId) {
return this.ready();
}
if (process.env.DEBUG === 'true') {
console.log('dueCards publication called for user:', userId, 'allUsers:', allUsers);
}
// Get user's board memberships for efficient filtering
const userBoards = ReactiveCache.getBoards({
members: userId
}).map(board => board._id);
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) {
check(sessionId, String);