Fix duplicated lists and do not show debug messages when env DEBUG is not true. Part 3.

Thanks to xet7 !

Fixes #5952
This commit is contained in:
Lauri Ojansivu 2025-10-21 15:31:34 +03:00
parent 1761f43afa
commit 58df525b49
5 changed files with 113 additions and 308 deletions

View file

@ -63,8 +63,10 @@ BlazeComponent.extendComponent({
title: 'Default', title: 'Default',
boardId: boardId, boardId: boardId,
}); });
if (process.env.DEBUG === 'true') {
console.log(`Created default swimlane ${swimlaneId} for board ${boardId}`); console.log(`Created default swimlane ${swimlaneId} for board ${boardId}`);
} }
}
this._swimlaneCreated.add(boardId); this._swimlaneCreated.add(boardId);
} else { } else {
this._swimlaneCreated.add(boardId); this._swimlaneCreated.add(boardId);
@ -123,6 +125,9 @@ BlazeComponent.extendComponent({
// Fix missing lists migration (for cards with wrong listId references) // Fix missing lists migration (for cards with wrong listId references)
await this.fixMissingLists(boardId); await this.fixMissingLists(boardId);
// Fix duplicate lists created by WeKan 8.10
await this.fixDuplicateLists(boardId);
// Start attachment migration in background if needed // Start attachment migration in background if needed
this.startAttachmentMigrationIfNeeded(boardId); this.startAttachmentMigrationIfNeeded(boardId);
} catch (error) { } catch (error) {
@ -312,14 +317,65 @@ BlazeComponent.extendComponent({
}); });
if (result && result.success) { if (result && result.success) {
if (process.env.DEBUG === 'true') {
console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`); console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`);
} }
}
} catch (error) { } catch (error) {
console.error('Error fixing missing lists:', error); console.error('Error fixing missing lists:', error);
} }
}, },
async fixDuplicateLists(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
if (!board) return;
// Check if board has already been processed for duplicate lists fix
if (board.fixDuplicateListsCompleted) {
if (process.env.DEBUG === 'true') {
console.log(`Board ${boardId} has already been processed for duplicate lists fix`);
}
return;
}
if (process.env.DEBUG === 'true') {
console.log(`Starting duplicate lists fix for board ${boardId}`);
}
// Execute the duplicate lists fix
const result = await new Promise((resolve, reject) => {
Meteor.call('fixDuplicateLists.fixBoard', boardId, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
if (result && result.fixed > 0) {
if (process.env.DEBUG === 'true') {
console.log(`Successfully fixed ${result.fixed} duplicate lists for board ${boardId}: ${result.fixedSwimlanes} swimlanes, ${result.fixedLists} lists`);
}
// Mark board as processed
Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
} else if (process.env.DEBUG === 'true') {
console.log(`No duplicate lists found for board ${boardId}`);
// Still mark as processed to avoid repeated checks
Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
} else {
// Still mark as processed to avoid repeated checks
Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
}
} catch (error) {
console.error('Error fixing duplicate lists:', error);
}
},
async startAttachmentMigrationIfNeeded(boardId) { async startAttachmentMigrationIfNeeded(boardId) {
try { try {
// Check if board has already been migrated // Check if board has already been migrated
@ -1201,10 +1257,14 @@ BlazeComponent.extendComponent({
const firstSwimlane = currentBoard.swimlanes()[0]; const firstSwimlane = currentBoard.swimlanes()[0];
Meteor.call('createCardWithDueDate', currentBoard._id, firstList._id, myTitle, startDate.toDate(), firstSwimlane._id, function(error, result) { Meteor.call('createCardWithDueDate', currentBoard._id, firstList._id, myTitle, startDate.toDate(), firstSwimlane._id, function(error, result) {
if (error) { if (error) {
if (process.env.DEBUG === 'true') {
console.log(error); console.log(error);
}
} else { } else {
if (process.env.DEBUG === 'true') {
console.log("Card Created", result); console.log("Card Created", result);
} }
}
}); });
closeModal(); closeModal();
} }

View file

@ -82,18 +82,26 @@ BlazeComponent.extendComponent({
}, },
'click .js-toggle-board-view': Popup.open('boardChangeView'), 'click .js-toggle-board-view': Popup.open('boardChangeView'),
'click .js-toggle-sidebar'() { 'click .js-toggle-sidebar'() {
if (process.env.DEBUG === 'true') {
console.log('Hamburger menu clicked'); console.log('Hamburger menu clicked');
}
// Use the same approach as keyboard shortcuts // Use the same approach as keyboard shortcuts
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') { if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Sidebar.toggle()'); console.log('Using Sidebar.toggle()');
}
Sidebar.toggle(); Sidebar.toggle();
} else { } else {
if (process.env.DEBUG === 'true') {
console.warn('Sidebar not available, trying alternative approach'); console.warn('Sidebar not available, trying alternative approach');
}
// Try to trigger the sidebar through the global Blaze helper // Try to trigger the sidebar through the global Blaze helper
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) { if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
const sidebar = Blaze._globalHelpers.Sidebar(); const sidebar = Blaze._globalHelpers.Sidebar();
if (sidebar && typeof sidebar.toggle === 'function') { if (sidebar && typeof sidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Blaze helper Sidebar.toggle()'); console.log('Using Blaze helper Sidebar.toggle()');
}
sidebar.toggle(); sidebar.toggle();
} }
} }

View file

@ -1,287 +0,0 @@
#!/usr/bin/env node
/**
* Standalone script to fix duplicate lists created by WeKan 8.10
*
* Usage:
* node fix-duplicate-lists.js
*
* This script will:
* 1. Connect to the MongoDB database
* 2. Identify boards with duplicate lists/swimlanes
* 3. Fix the duplicates by merging them
* 4. Report the results
*/
const { MongoClient } = require('mongodb');
// Configuration - adjust these for your setup
const MONGO_URL = process.env.MONGO_URL || 'mongodb://localhost:27017/wekan';
const DB_NAME = process.env.MONGO_DB_NAME || 'wekan';
class DuplicateListsFixer {
constructor() {
this.client = null;
this.db = null;
}
async connect() {
console.log('Connecting to MongoDB...');
this.client = new MongoClient(MONGO_URL);
await this.client.connect();
this.db = this.client.db(DB_NAME);
console.log('Connected to MongoDB');
}
async disconnect() {
if (this.client) {
await this.client.close();
console.log('Disconnected from MongoDB');
}
}
async getReport() {
console.log('Analyzing boards for duplicate lists...');
const boards = await this.db.collection('boards').find({}).toArray();
const report = [];
for (const board of boards) {
const swimlanes = await this.db.collection('swimlanes').find({ boardId: board._id }).toArray();
const lists = await this.db.collection('lists').find({ boardId: board._id }).toArray();
// Check for duplicate swimlanes
const swimlaneGroups = {};
swimlanes.forEach(swimlane => {
const key = swimlane.title || 'Default';
if (!swimlaneGroups[key]) {
swimlaneGroups[key] = [];
}
swimlaneGroups[key].push(swimlane);
});
// Check for duplicate lists
const listGroups = {};
lists.forEach(list => {
const key = `${list.swimlaneId || 'null'}-${list.title}`;
if (!listGroups[key]) {
listGroups[key] = [];
}
listGroups[key].push(list);
});
const duplicateSwimlanes = Object.values(swimlaneGroups).filter(group => group.length > 1);
const duplicateLists = Object.values(listGroups).filter(group => group.length > 1);
if (duplicateSwimlanes.length > 0 || duplicateLists.length > 0) {
report.push({
boardId: board._id,
boardTitle: board.title,
duplicateSwimlanes: duplicateSwimlanes.length,
duplicateLists: duplicateLists.length,
totalSwimlanes: swimlanes.length,
totalLists: lists.length
});
}
}
return {
totalBoards: boards.length,
boardsWithDuplicates: report.length,
report
};
}
async fixBoard(boardId) {
console.log(`Fixing duplicate lists for board ${boardId}...`);
// Fix duplicate swimlanes
const swimlaneResult = await this.fixDuplicateSwimlanes(boardId);
// Fix duplicate lists
const listResult = await this.fixDuplicateLists(boardId);
return {
boardId,
fixedSwimlanes: swimlaneResult.fixed,
fixedLists: listResult.fixed,
fixed: swimlaneResult.fixed + listResult.fixed
};
}
async fixDuplicateSwimlanes(boardId) {
const swimlanes = await this.db.collection('swimlanes').find({ boardId }).toArray();
const swimlaneGroups = {};
let fixed = 0;
// Group swimlanes by title
swimlanes.forEach(swimlane => {
const key = swimlane.title || 'Default';
if (!swimlaneGroups[key]) {
swimlaneGroups[key] = [];
}
swimlaneGroups[key].push(swimlane);
});
// For each group with duplicates, keep the oldest and remove the rest
for (const [title, group] of Object.entries(swimlaneGroups)) {
if (group.length > 1) {
// Sort by creation date, keep the oldest
group.sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
const keepSwimlane = group[0];
const removeSwimlanes = group.slice(1);
console.log(`Found ${group.length} duplicate swimlanes with title "${title}", keeping oldest (${keepSwimlane._id})`);
// Move all lists from duplicate swimlanes to the kept swimlane
for (const swimlane of removeSwimlanes) {
const lists = await this.db.collection('lists').find({ swimlaneId: swimlane._id }).toArray();
for (const list of lists) {
// Check if a list with the same title already exists in the kept swimlane
const existingList = await this.db.collection('lists').findOne({
boardId,
swimlaneId: keepSwimlane._id,
title: list.title
});
if (existingList) {
// Move cards to existing list
await this.db.collection('cards').updateMany(
{ listId: list._id },
{ $set: { listId: existingList._id } }
);
// Remove duplicate list
await this.db.collection('lists').deleteOne({ _id: list._id });
console.log(`Moved cards from duplicate list "${list.title}" to existing list in kept swimlane`);
} else {
// Move list to kept swimlane
await this.db.collection('lists').updateOne(
{ _id: list._id },
{ $set: { swimlaneId: keepSwimlane._id } }
);
console.log(`Moved list "${list.title}" to kept swimlane`);
}
}
// Remove duplicate swimlane
await this.db.collection('swimlanes').deleteOne({ _id: swimlane._id });
fixed++;
}
}
}
return { fixed };
}
async fixDuplicateLists(boardId) {
const lists = await this.db.collection('lists').find({ boardId }).toArray();
const listGroups = {};
let fixed = 0;
// Group lists by title and swimlaneId
lists.forEach(list => {
const key = `${list.swimlaneId || 'null'}-${list.title}`;
if (!listGroups[key]) {
listGroups[key] = [];
}
listGroups[key].push(list);
});
// For each group with duplicates, keep the oldest and remove the rest
for (const [key, group] of Object.entries(listGroups)) {
if (group.length > 1) {
// Sort by creation date, keep the oldest
group.sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
const keepList = group[0];
const removeLists = group.slice(1);
console.log(`Found ${group.length} duplicate lists with title "${keepList.title}" in swimlane ${keepList.swimlaneId}, keeping oldest (${keepList._id})`);
// Move all cards from duplicate lists to the kept list
for (const list of removeLists) {
await this.db.collection('cards').updateMany(
{ listId: list._id },
{ $set: { listId: keepList._id } }
);
// Remove duplicate list
await this.db.collection('lists').deleteOne({ _id: list._id });
fixed++;
console.log(`Moved cards from duplicate list "${list.title}" to kept list`);
}
}
}
return { fixed };
}
async fixAllBoards() {
console.log('Starting duplicate lists fix for all boards...');
const allBoards = await this.db.collection('boards').find({}).toArray();
let totalFixed = 0;
let totalBoardsProcessed = 0;
for (const board of allBoards) {
try {
const result = await this.fixBoard(board._id);
totalFixed += result.fixed;
totalBoardsProcessed++;
if (result.fixed > 0) {
console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`);
}
} catch (error) {
console.error(`Error fixing board ${board._id}:`, error);
}
}
console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`);
return {
message: `Fixed ${totalFixed} duplicate lists across ${totalBoardsProcessed} boards`,
totalFixed,
totalBoardsProcessed
};
}
}
// Main execution
async function main() {
const fixer = new DuplicateListsFixer();
try {
await fixer.connect();
// Get report first
const report = await fixer.getReport();
if (report.boardsWithDuplicates === 0) {
console.log('No duplicate lists found!');
return;
}
console.log(`Found ${report.boardsWithDuplicates} boards with duplicate lists:`);
report.report.forEach(board => {
console.log(`- Board "${board.boardTitle}" (${board.boardId}): ${board.duplicateSwimlanes} duplicate swimlanes, ${board.duplicateLists} duplicate lists`);
});
// Perform the fix
const result = await fixer.fixAllBoards();
console.log('Fix completed:', result);
} catch (error) {
console.error('Error:', error);
process.exit(1);
} finally {
await fixer.disconnect();
}
}
// Run if called directly
if (require.main === module) {
main();
}
module.exports = DuplicateListsFixer;

View file

@ -15,7 +15,9 @@ Meteor.methods({
throw new Meteor.Error('not-authorized'); throw new Meteor.Error('not-authorized');
} }
if (process.env.DEBUG === 'true') {
console.log('Starting duplicate lists fix for all boards...'); console.log('Starting duplicate lists fix for all boards...');
}
const allBoards = Boards.find({}).fetch(); const allBoards = Boards.find({}).fetch();
let totalFixed = 0; let totalFixed = 0;
@ -27,7 +29,7 @@ Meteor.methods({
totalFixed += result.fixed; totalFixed += result.fixed;
totalBoardsProcessed++; totalBoardsProcessed++;
if (result.fixed > 0) { if (result.fixed > 0 && process.env.DEBUG === 'true') {
console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`); console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`);
} }
} catch (error) { } catch (error) {
@ -35,7 +37,9 @@ Meteor.methods({
} }
} }
if (process.env.DEBUG === 'true') {
console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`); console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`);
}
return { return {
message: `Fixed ${totalFixed} duplicate lists across ${totalBoardsProcessed} boards`, message: `Fixed ${totalFixed} duplicate lists across ${totalBoardsProcessed} boards`,
@ -55,7 +59,9 @@ Meteor.methods({
}, },
fixDuplicateListsForBoard(boardId) { fixDuplicateListsForBoard(boardId) {
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 = this.fixDuplicateSwimlanes(boardId);
@ -94,7 +100,9 @@ Meteor.methods({
const keepSwimlane = group[0]; const keepSwimlane = group[0];
const removeSwimlanes = group.slice(1); const removeSwimlanes = group.slice(1);
if (process.env.DEBUG === 'true') {
console.log(`Found ${group.length} duplicate swimlanes with title "${title}", keeping oldest (${keepSwimlane._id})`); console.log(`Found ${group.length} duplicate swimlanes with title "${title}", keeping oldest (${keepSwimlane._id})`);
}
// Move all lists from duplicate swimlanes to the kept swimlane // Move all lists from duplicate swimlanes to the kept swimlane
removeSwimlanes.forEach(swimlane => { removeSwimlanes.forEach(swimlane => {
@ -116,12 +124,16 @@ Meteor.methods({
); );
// Remove duplicate list // Remove duplicate list
Lists.remove(list._id); Lists.remove(list._id);
if (process.env.DEBUG === 'true') {
console.log(`Moved cards from duplicate list "${list.title}" to existing list in kept swimlane`); console.log(`Moved cards from duplicate list "${list.title}" to existing list in kept swimlane`);
}
} else { } else {
// Move list to kept swimlane // Move list to kept swimlane
Lists.update(list._id, { $set: { swimlaneId: keepSwimlane._id } }); Lists.update(list._id, { $set: { swimlaneId: keepSwimlane._id } });
if (process.env.DEBUG === 'true') {
console.log(`Moved list "${list.title}" to kept swimlane`); console.log(`Moved list "${list.title}" to kept swimlane`);
} }
}
}); });
// Remove duplicate swimlane // Remove duplicate swimlane
@ -157,7 +169,9 @@ Meteor.methods({
const keepList = group[0]; const keepList = group[0];
const removeLists = group.slice(1); const removeLists = group.slice(1);
if (process.env.DEBUG === 'true') {
console.log(`Found ${group.length} duplicate lists with title "${keepList.title}" in swimlane ${keepList.swimlaneId}, keeping oldest (${keepList._id})`); console.log(`Found ${group.length} duplicate lists with title "${keepList.title}" in swimlane ${keepList.swimlaneId}, keeping oldest (${keepList._id})`);
}
// Move all cards from duplicate lists to the kept list // Move all cards from duplicate lists to the kept list
removeLists.forEach(list => { removeLists.forEach(list => {
@ -170,7 +184,9 @@ Meteor.methods({
// Remove duplicate list // Remove duplicate list
Lists.remove(list._id); Lists.remove(list._id);
fixed++; fixed++;
if (process.env.DEBUG === 'true') {
console.log(`Moved cards from duplicate list "${list.title}" to kept list`); console.log(`Moved cards from duplicate list "${list.title}" to kept list`);
}
}); });
} }
}); });

View file

@ -55,7 +55,9 @@ class FixMissingListsMigration {
for (const card of cards) { for (const card of cards) {
const expectedSwimlaneId = listSwimlaneMap.get(card.listId); const expectedSwimlaneId = listSwimlaneMap.get(card.listId);
if (expectedSwimlaneId && expectedSwimlaneId !== card.swimlaneId) { if (expectedSwimlaneId && expectedSwimlaneId !== card.swimlaneId) {
if (process.env.DEBUG === 'true') {
console.log(`Found mismatched card: ${card._id}, listId: ${card.listId}, card swimlaneId: ${card.swimlaneId}, list swimlaneId: ${expectedSwimlaneId}`); console.log(`Found mismatched card: ${card._id}, listId: ${card.listId}, card swimlaneId: ${card.swimlaneId}, list swimlaneId: ${expectedSwimlaneId}`);
}
return true; return true;
} }
} }
@ -72,7 +74,9 @@ class FixMissingListsMigration {
*/ */
async executeMigration(boardId) { async executeMigration(boardId) {
try { try {
if (process.env.DEBUG === 'true') {
console.log(`Starting fix missing lists migration for board ${boardId}`); console.log(`Starting fix missing lists migration for board ${boardId}`);
}
const board = ReactiveCache.getBoard(boardId); const board = ReactiveCache.getBoard(boardId);
if (!board) { if (!board) {
@ -165,8 +169,10 @@ class FixMissingListsMigration {
targetList = { _id: newListId, ...newListData }; targetList = { _id: newListId, ...newListData };
createdLists++; createdLists++;
if (process.env.DEBUG === 'true') {
console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`); console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`);
} }
}
// Update all cards in this group to use the correct listId // Update all cards in this group to use the correct listId
for (const card of cardsInList) { for (const card of cardsInList) {
@ -189,7 +195,9 @@ class FixMissingListsMigration {
} }
}); });
if (process.env.DEBUG === 'true') {
console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`); console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`);
}
return { return {
success: true, success: true,