Browse Source

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

Thanks to xet7 !

Fixes #5955
Lauri Ojansivu 1 day ago
parent
commit
347fa9e5cd

+ 0 - 27
client/components/cards/cardDate.js

@@ -246,57 +246,30 @@ 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;
   }
 

+ 59 - 92
client/components/main/dueCards.js

@@ -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);
-  }
-  
-  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...');
+    // Subscribe to the optimized due cards publication
+    this.autorun(() => {
+      const allUsers = this.dueCardsView() === 'all';
+      if (this.subscriptionHandle) {
+        this.subscriptionHandle.stop();
       }
-      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,
-    });
-    // queryParams[OPERATOR_LIMIT] = 5;
-    queryParams.addPredicate(OPERATOR_SORT, {
-      name: PREDICATE_DUE_AT,
-      order: ORDER_ASCENDING,
+      this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
     });
+  }
 
-    // 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'));
+  onDestroyed() {
+    super.onDestroyed();
+    if (this.subscriptionHandle) {
+      this.subscriptionHandle.stop();
     }
-    
-    this.runGlobalSearch(queryParams);
   }
 
   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;
     }
 
-    // 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');
-
-      // 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();
+    // 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, ''] }
     });
 
-    // eslint-disable-next-line no-console
-    console.log('cards sorted by due date (oldest first):', cards);
-    return cards;
+    // 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;
+      });
+    }
+
+    // Cache the results for 5 seconds to avoid re-filtering on every render
+    this._cachedCards = filteredCards;
+    this._cachedTimestamp = Date.now();
+
+    return filteredCards;
   }
 }
 

+ 6 - 4
client/lib/cardSearch.js

@@ -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);
           }
         });

+ 63 - 20
server/publications/cards.js

@@ -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);