mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
Fix Regression - unable to view cards by due date v8.11.
Thanks to xet7 ! Fixes #5964
This commit is contained in:
parent
8e296231ba
commit
ae11e80bde
6 changed files with 184 additions and 27 deletions
|
|
@ -187,7 +187,7 @@ BlazeComponent.extendComponent({
|
||||||
console.log(`Board ${boardId} has no shared lists to convert`);
|
console.log(`Board ${boardId} has no shared lists to convert`);
|
||||||
}
|
}
|
||||||
// Mark as processed even if no shared lists
|
// Mark as processed even if no shared lists
|
||||||
Meteor.call('boards.update', boardId, { $set: { hasSharedListsConverted: true } });
|
Boards.update(boardId, { $set: { hasSharedListsConverted: true } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +259,7 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark board as processed
|
// Mark board as processed
|
||||||
Meteor.call('boards.update', boardId, { $set: { hasSharedListsConverted: true } });
|
Boards.update(boardId, { $set: { hasSharedListsConverted: true } });
|
||||||
|
|
||||||
if (process.env.DEBUG === 'true') {
|
if (process.env.DEBUG === 'true') {
|
||||||
console.log(`Successfully converted ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`);
|
console.log(`Successfully converted ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`);
|
||||||
|
|
@ -361,14 +361,14 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark board as processed
|
// Mark board as processed
|
||||||
Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
|
Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
|
||||||
} else if (process.env.DEBUG === 'true') {
|
} else if (process.env.DEBUG === 'true') {
|
||||||
console.log(`No duplicate lists found for board ${boardId}`);
|
console.log(`No duplicate lists found for board ${boardId}`);
|
||||||
// Still mark as processed to avoid repeated checks
|
// Still mark as processed to avoid repeated checks
|
||||||
Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
|
Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
|
||||||
} else {
|
} else {
|
||||||
// Still mark as processed to avoid repeated checks
|
// Still mark as processed to avoid repeated checks
|
||||||
Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
|
Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,16 @@ template(name="dueCards")
|
||||||
span.global-search-error-messages
|
span.global-search-error-messages
|
||||||
= msg
|
= msg
|
||||||
else
|
else
|
||||||
|
.due-cards-results-header
|
||||||
|
h1
|
||||||
|
= resultsText
|
||||||
each card in dueCardsList
|
each card in dueCardsList
|
||||||
+resultCard(card)
|
+resultCard(card)
|
||||||
|
else
|
||||||
|
.global-search-results-list-wrapper
|
||||||
|
.no-results
|
||||||
|
h3 {{_ 'dueCards-noResults-title'}}
|
||||||
|
p {{_ 'dueCards-noResults-description'}}
|
||||||
|
|
||||||
template(name="dueCardsViewChangePopup")
|
template(name="dueCardsViewChangePopup")
|
||||||
if currentUser
|
if currentUser
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
|
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
|
||||||
|
import { TAPi18n } from '/imports/i18n';
|
||||||
|
|
||||||
// const subManager = new SubsManager();
|
// const subManager = new SubsManager();
|
||||||
|
|
||||||
|
|
@ -24,22 +25,25 @@ Template.dueCards.helpers({
|
||||||
return Meteor.userId();
|
return Meteor.userId();
|
||||||
},
|
},
|
||||||
dueCardsList() {
|
dueCardsList() {
|
||||||
const component = BlazeComponent.getComponentForElement(this);
|
const component = BlazeComponent.getComponentForElement(this.firstNode);
|
||||||
if (component && component.dueCardsList) {
|
if (component && component.dueCardsList) {
|
||||||
return component.dueCardsList();
|
return component.dueCardsList();
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
hasResults() {
|
hasResults() {
|
||||||
const component = BlazeComponent.getComponentForElement(this);
|
const component = BlazeComponent.getComponentForElement(this.firstNode);
|
||||||
if (component && component.dueCardsList) {
|
if (component && component.hasResults) {
|
||||||
const cards = component.dueCardsList();
|
return component.hasResults.get();
|
||||||
return cards && cards.length > 0;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
searching() {
|
searching() {
|
||||||
return false; // No longer using search, so always false
|
const component = BlazeComponent.getComponentForElement(this.firstNode);
|
||||||
|
if (component && component.isLoading) {
|
||||||
|
return component.isLoading.get();
|
||||||
|
}
|
||||||
|
return true; // Show loading by default
|
||||||
},
|
},
|
||||||
hasQueryErrors() {
|
hasQueryErrors() {
|
||||||
return false; // No longer using search, so always false
|
return false; // No longer using search, so always false
|
||||||
|
|
@ -47,6 +51,20 @@ Template.dueCards.helpers({
|
||||||
errorMessages() {
|
errorMessages() {
|
||||||
return []; // No longer using search, so always empty
|
return []; // No longer using search, so always empty
|
||||||
},
|
},
|
||||||
|
cardsCount() {
|
||||||
|
const component = BlazeComponent.getComponentForElement(this.firstNode);
|
||||||
|
if (component && component.cardsCount) {
|
||||||
|
return component.cardsCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
resultsText() {
|
||||||
|
const component = BlazeComponent.getComponentForElement(this.firstNode);
|
||||||
|
if (component && component.resultsText) {
|
||||||
|
return component.resultsText();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
|
|
@ -78,6 +96,9 @@ class DueCardsComponent extends BlazeComponent {
|
||||||
this._cachedCards = null;
|
this._cachedCards = null;
|
||||||
this._cachedTimestamp = null;
|
this._cachedTimestamp = null;
|
||||||
this.subscriptionHandle = null;
|
this.subscriptionHandle = null;
|
||||||
|
this.isLoading = new ReactiveVar(true);
|
||||||
|
this.hasResults = new ReactiveVar(false);
|
||||||
|
this.searching = new ReactiveVar(false);
|
||||||
|
|
||||||
// Subscribe to the optimized due cards publication
|
// Subscribe to the optimized due cards publication
|
||||||
this.autorun(() => {
|
this.autorun(() => {
|
||||||
|
|
@ -86,6 +107,24 @@ class DueCardsComponent extends BlazeComponent {
|
||||||
this.subscriptionHandle.stop();
|
this.subscriptionHandle.stop();
|
||||||
}
|
}
|
||||||
this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
|
this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
|
||||||
|
|
||||||
|
// Update loading state based on subscription
|
||||||
|
this.autorun(() => {
|
||||||
|
if (this.subscriptionHandle && this.subscriptionHandle.ready()) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards: subscription ready, loading data...');
|
||||||
|
}
|
||||||
|
this.isLoading.set(false);
|
||||||
|
const cards = this.dueCardsList();
|
||||||
|
this.hasResults.set(cards && cards.length > 0);
|
||||||
|
} else {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards: subscription not ready, showing loading...');
|
||||||
|
}
|
||||||
|
this.isLoading.set(true);
|
||||||
|
this.hasResults.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,9 +145,45 @@ class DueCardsComponent extends BlazeComponent {
|
||||||
return this.dueCardsView() === 'board';
|
return this.dueCardsView() === 'board';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasResults() {
|
||||||
|
return this.hasResults.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
cardsCount() {
|
||||||
|
const cards = this.dueCardsList();
|
||||||
|
return cards ? cards.length : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsText() {
|
||||||
|
const count = this.cardsCount();
|
||||||
|
if (count === 1) {
|
||||||
|
return TAPi18n.__('one-card-found');
|
||||||
|
} else {
|
||||||
|
// Get the translated text and manually replace %s with the count
|
||||||
|
const baseText = TAPi18n.__('n-cards-found');
|
||||||
|
const result = baseText.replace('%s', count);
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards: base text:', baseText, 'count:', count, 'result:', result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dueCardsList() {
|
dueCardsList() {
|
||||||
|
// Check if subscription is ready
|
||||||
|
if (!this.subscriptionHandle || !this.subscriptionHandle.ready()) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards client: subscription not ready');
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
// Use cached results if available to avoid expensive re-sorting
|
// Use cached results if available to avoid expensive re-sorting
|
||||||
if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
|
if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards client: using cached results,', this._cachedCards.length, 'cards');
|
||||||
|
}
|
||||||
return this._cachedCards;
|
return this._cachedCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,23 +194,56 @@ class DueCardsComponent extends BlazeComponent {
|
||||||
dueAt: { $exists: true, $nin: [null, ''] }
|
dueAt: { $exists: true, $nin: [null, ''] }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards client: found', cards.length, 'cards with due dates');
|
||||||
|
console.log('dueCards client: cards details:', cards.map(c => ({
|
||||||
|
id: c._id,
|
||||||
|
title: c.title,
|
||||||
|
dueAt: c.dueAt,
|
||||||
|
boardId: c.boardId,
|
||||||
|
members: c.members,
|
||||||
|
assignees: c.assignees,
|
||||||
|
userId: c.userId
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
// Filter cards based on user view preference
|
// Filter cards based on user view preference
|
||||||
const allUsers = this.dueCardsView() === 'all';
|
const allUsers = this.dueCardsView() === 'all';
|
||||||
const currentUser = ReactiveCache.getCurrentUser();
|
const currentUser = ReactiveCache.getCurrentUser();
|
||||||
let filteredCards = cards;
|
let filteredCards = cards;
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards client: current user:', currentUser ? currentUser._id : 'none');
|
||||||
|
console.log('dueCards client: showing all users:', allUsers);
|
||||||
|
}
|
||||||
|
|
||||||
if (!allUsers && currentUser) {
|
if (!allUsers && currentUser) {
|
||||||
filteredCards = cards.filter(card => {
|
filteredCards = cards.filter(card => {
|
||||||
return card.members && card.members.includes(currentUser._id) ||
|
const isMember = card.members && card.members.includes(currentUser._id);
|
||||||
card.assignees && card.assignees.includes(currentUser._id) ||
|
const isAssignee = card.assignees && card.assignees.includes(currentUser._id);
|
||||||
card.userId === currentUser._id;
|
const isAuthor = card.userId === currentUser._id;
|
||||||
|
const matches = isMember || isAssignee || isAuthor;
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true' && matches) {
|
||||||
|
console.log('dueCards client: card matches user:', card.title, { isMember, isAssignee, isAuthor });
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards client: filtered to', filteredCards.length, 'cards');
|
||||||
|
}
|
||||||
|
|
||||||
// Cache the results for 5 seconds to avoid re-filtering on every render
|
// Cache the results for 5 seconds to avoid re-filtering on every render
|
||||||
this._cachedCards = filteredCards;
|
this._cachedCards = filteredCards;
|
||||||
this._cachedTimestamp = Date.now();
|
this._cachedTimestamp = Date.now();
|
||||||
|
|
||||||
|
// Update reactive variables
|
||||||
|
this.hasResults.set(filteredCards && filteredCards.length > 0);
|
||||||
|
this.isLoading.set(false);
|
||||||
|
|
||||||
return filteredCards;
|
return filteredCards;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1015,6 +1015,8 @@
|
||||||
"dueCardsViewChange-choice-me": "Me",
|
"dueCardsViewChange-choice-me": "Me",
|
||||||
"dueCardsViewChange-choice-all": "All Users",
|
"dueCardsViewChange-choice-all": "All Users",
|
||||||
"dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.",
|
"dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.",
|
||||||
|
"dueCards-noResults-title": "No Due Cards Found",
|
||||||
|
"dueCards-noResults-description": "You don't have any cards with due dates at the moment.",
|
||||||
"broken-cards": "Broken Cards",
|
"broken-cards": "Broken Cards",
|
||||||
"board-title-not-found": "Board '%s' not found.",
|
"board-title-not-found": "Board '%s' not found.",
|
||||||
"swimlane-title-not-found": "Swimlane '%s' not found.",
|
"swimlane-title-not-found": "Swimlane '%s' not found.",
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Meteor.methods({
|
||||||
|
|
||||||
for (const board of allBoards) {
|
for (const board of allBoards) {
|
||||||
try {
|
try {
|
||||||
const result = this.fixDuplicateListsForBoard(board._id);
|
const result = fixDuplicateListsForBoard(board._id);
|
||||||
totalFixed += result.fixed;
|
totalFixed += result.fixed;
|
||||||
totalBoardsProcessed++;
|
totalBoardsProcessed++;
|
||||||
|
|
||||||
|
|
@ -55,19 +55,21 @@ Meteor.methods({
|
||||||
throw new Meteor.Error('not-authorized');
|
throw new Meteor.Error('not-authorized');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fixDuplicateListsForBoard(boardId);
|
return fixDuplicateListsForBoard(boardId);
|
||||||
},
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fixDuplicateListsForBoard(boardId) {
|
// Helper functions defined outside of Meteor.methods
|
||||||
|
function fixDuplicateListsForBoard(boardId) {
|
||||||
if (process.env.DEBUG === 'true') {
|
if (process.env.DEBUG === 'true') {
|
||||||
console.log(`Fixing duplicate lists for board ${boardId}...`);
|
console.log(`Fixing duplicate lists for board ${boardId}...`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, fix duplicate swimlanes
|
// First, fix duplicate swimlanes
|
||||||
const swimlaneResult = this.fixDuplicateSwimlanes(boardId);
|
const swimlaneResult = fixDuplicateSwimlanes(boardId);
|
||||||
|
|
||||||
// Then, fix duplicate lists
|
// Then, fix duplicate lists
|
||||||
const listResult = this.fixDuplicateLists(boardId);
|
const listResult = fixDuplicateLists(boardId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
boardId,
|
boardId,
|
||||||
|
|
@ -75,9 +77,10 @@ Meteor.methods({
|
||||||
fixedLists: listResult.fixed,
|
fixedLists: listResult.fixed,
|
||||||
fixed: swimlaneResult.fixed + listResult.fixed
|
fixed: swimlaneResult.fixed + listResult.fixed
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
fixDuplicateSwimlanes(boardId) {
|
// Helper functions defined outside of Meteor.methods
|
||||||
|
function fixDuplicateSwimlanes(boardId) {
|
||||||
const swimlanes = Swimlanes.find({ boardId }).fetch();
|
const swimlanes = Swimlanes.find({ boardId }).fetch();
|
||||||
const swimlaneGroups = {};
|
const swimlaneGroups = {};
|
||||||
let fixed = 0;
|
let fixed = 0;
|
||||||
|
|
@ -144,9 +147,9 @@ Meteor.methods({
|
||||||
});
|
});
|
||||||
|
|
||||||
return { fixed };
|
return { fixed };
|
||||||
},
|
}
|
||||||
|
|
||||||
fixDuplicateLists(boardId) {
|
function fixDuplicateLists(boardId) {
|
||||||
const lists = Lists.find({ boardId }).fetch();
|
const lists = Lists.find({ boardId }).fetch();
|
||||||
const listGroups = {};
|
const listGroups = {};
|
||||||
let fixed = 0;
|
let fixed = 0;
|
||||||
|
|
@ -192,8 +195,9 @@ Meteor.methods({
|
||||||
});
|
});
|
||||||
|
|
||||||
return { fixed };
|
return { fixed };
|
||||||
},
|
}
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
'fixDuplicateLists.getReport'() {
|
'fixDuplicateLists.getReport'() {
|
||||||
if (!this.userId) {
|
if (!this.userId) {
|
||||||
throw new Meteor.Error('not-authorized');
|
throw new Meteor.Error('not-authorized');
|
||||||
|
|
|
||||||
|
|
@ -136,10 +136,29 @@ Meteor.publish('dueCards', function(allUsers = false) {
|
||||||
|
|
||||||
// Get user's board memberships for efficient filtering
|
// Get user's board memberships for efficient filtering
|
||||||
const userBoards = ReactiveCache.getBoards({
|
const userBoards = ReactiveCache.getBoards({
|
||||||
members: userId
|
$or: [
|
||||||
|
{ permission: 'public' },
|
||||||
|
{ members: { $elemMatch: { userId, isActive: true } } }
|
||||||
|
]
|
||||||
}).map(board => board._id);
|
}).map(board => board._id);
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards userBoards:', userBoards);
|
||||||
|
console.log('dueCards userBoards count:', userBoards.length);
|
||||||
|
|
||||||
|
// Also check if there are any cards with due dates in the system at all
|
||||||
|
const allCardsWithDueDates = Cards.find({
|
||||||
|
type: 'cardType-card',
|
||||||
|
archived: false,
|
||||||
|
dueAt: { $exists: true, $nin: [null, ''] }
|
||||||
|
}).count();
|
||||||
|
console.log('dueCards: total cards with due dates in system:', allCardsWithDueDates);
|
||||||
|
}
|
||||||
|
|
||||||
if (userBoards.length === 0) {
|
if (userBoards.length === 0) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('dueCards: No boards found for user, returning ready');
|
||||||
|
}
|
||||||
return this.ready();
|
return this.ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,7 +201,23 @@ Meteor.publish('dueCards', function(allUsers = false) {
|
||||||
console.log('dueCards options:', JSON.stringify(options, null, 2));
|
console.log('dueCards options:', JSON.stringify(options, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Cards.find(selector, options);
|
const result = Cards.find(selector, options);
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
const count = result.count();
|
||||||
|
console.log('dueCards publication: returning', count, 'cards');
|
||||||
|
if (count > 0) {
|
||||||
|
const sampleCards = result.fetch().slice(0, 3);
|
||||||
|
console.log('dueCards publication: sample cards:', sampleCards.map(c => ({
|
||||||
|
id: c._id,
|
||||||
|
title: c.title,
|
||||||
|
dueAt: c.dueAt,
|
||||||
|
boardId: c.boardId
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.publish('globalSearch', function(sessionId, params, text) {
|
Meteor.publish('globalSearch', function(sessionId, params, text) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue