When opening board, migrate from Shared Lists to Per-Swimlane Lists.

Thanks to xet7 !

Fixes #5952
This commit is contained in:
Lauri Ojansivu 2025-10-20 00:22:26 +03:00
parent 48b645ee1e
commit 1e6252de7f
8 changed files with 112 additions and 219 deletions

View file

@ -40,8 +40,6 @@ if (errors.length > 0) {
// Import cron job storage for persistent job tracking
import './cronJobStorage';
// Import board migration detector for automatic board migrations
import './boardMigrationDetector';
// Import cron migration manager for cron-based migrations
import './cronMigrationManager';
// Note: Automatic migrations are disabled - migrations only run when opening boards
// import './boardMigrationDetector';
// import './cronMigrationManager';

View file

@ -337,13 +337,13 @@ class BoardMigrationDetector {
// Export singleton instance
export const boardMigrationDetector = new BoardMigrationDetector();
// Start the detector on server startup
Meteor.startup(() => {
// Wait a bit for the system to initialize
Meteor.setTimeout(() => {
boardMigrationDetector.start();
}, 10000); // Start after 10 seconds
});
// Note: Automatic migration detector is disabled - migrations only run when opening boards
// Meteor.startup(() => {
// // Wait a bit for the system to initialize
// Meteor.setTimeout(() => {
// boardMigrationDetector.start();
// }, 10000); // Start after 10 seconds
// });
// Meteor methods for client access
Meteor.methods({

View file

@ -233,17 +233,6 @@ class CronMigrationManager {
schedule: 'every 1 minute',
status: 'stopped'
},
{
id: 'restore-legacy-lists',
name: 'Restore Legacy Lists',
description: 'Restore legacy lists to their original shared state across all swimlanes',
weight: 3,
completed: false,
progress: 0,
cronName: 'migration_restore_legacy_lists',
schedule: 'every 1 minute',
status: 'stopped'
}
];
}
@ -441,14 +430,6 @@ class CronMigrationManager {
{ name: 'Cleanup old data', duration: 1000 }
);
break;
case 'restore-legacy-lists':
steps.push(
{ name: 'Identify legacy lists', duration: 1000 },
{ name: 'Restore lists to shared state', duration: 2000 },
{ name: 'Update board settings', duration: 500 },
{ name: 'Verify restoration', duration: 500 }
);
break;
default:
steps.push(
{ name: `Execute ${step.name}`, duration: 2000 },
@ -465,10 +446,7 @@ class CronMigrationManager {
async executeMigrationStep(jobId, stepIndex, stepData, stepId) {
const { name, duration } = stepData;
if (stepId === 'restore-legacy-lists') {
await this.executeRestoreLegacyListsMigration(jobId, stepIndex, stepData);
} else {
// Simulate step execution with progress updates for other migrations
// Simulate step execution with progress updates for other migrations
const progressSteps = 10;
for (let i = 0; i <= progressSteps; i++) {
const progress = Math.round((i / progressSteps) * 100);
@ -485,95 +463,6 @@ class CronMigrationManager {
}
}
/**
* Execute the restore legacy lists migration
*/
async executeRestoreLegacyListsMigration(jobId, stepIndex, stepData) {
const { name } = stepData;
try {
// Import collections directly for server-side access
const { default: Boards } = await import('/models/boards');
const { default: Lists } = await import('/models/lists');
// Step 1: Identify legacy lists
cronJobStorage.saveJobStep(jobId, stepIndex, {
progress: 25,
currentAction: 'Identifying legacy lists...'
});
const boards = Boards.find({}).fetch();
const migrationDate = new Date('2025-10-10T21:14:44.000Z'); // Date of commit 719ef87efceacfe91461a8eeca7cf74d11f4cc0a
let totalLegacyLists = 0;
for (const board of boards) {
const allLists = Lists.find({ boardId: board._id }).fetch();
const legacyLists = allLists.filter(list => {
const listDate = list.createdAt || new Date(0);
return listDate < migrationDate && list.swimlaneId && list.swimlaneId !== '';
});
totalLegacyLists += legacyLists.length;
}
// Step 2: Restore lists to shared state
cronJobStorage.saveJobStep(jobId, stepIndex, {
progress: 50,
currentAction: 'Restoring lists to shared state...'
});
let restoredCount = 0;
for (const board of boards) {
const allLists = Lists.find({ boardId: board._id }).fetch();
const legacyLists = allLists.filter(list => {
const listDate = list.createdAt || new Date(0);
return listDate < migrationDate && list.swimlaneId && list.swimlaneId !== '';
});
// Restore legacy lists to shared state (empty swimlaneId)
for (const list of legacyLists) {
Lists.direct.update(list._id, {
$set: {
swimlaneId: ''
}
});
restoredCount++;
}
// Mark the board as having legacy lists
if (legacyLists.length > 0) {
Boards.direct.update(board._id, {
$set: {
hasLegacyLists: true
}
});
}
}
// Step 3: Update board settings
cronJobStorage.saveJobStep(jobId, stepIndex, {
progress: 75,
currentAction: 'Updating board settings...'
});
// Step 4: Verify restoration
cronJobStorage.saveJobStep(jobId, stepIndex, {
progress: 100,
currentAction: `Verification complete. Restored ${restoredCount} legacy lists.`
});
console.log(`Successfully restored ${restoredCount} legacy lists across ${boards.length} boards`);
} catch (error) {
console.error('Error during restore legacy lists migration:', error);
cronJobStorage.saveJobStep(jobId, stepIndex, {
progress: 0,
currentAction: `Error: ${error.message}`,
status: 'error'
});
throw error;
}
}
/**
* Execute a board operation job
@ -1420,53 +1309,6 @@ class CronMigrationManager {
return stats;
}
/**
* Trigger restore legacy lists migration
*/
async triggerRestoreLegacyListsMigration() {
try {
// Find the restore legacy lists step
const step = this.migrationSteps.find(s => s.id === 'restore-legacy-lists');
if (!step) {
throw new Error('Restore legacy lists migration step not found');
}
// Create a job for this migration
const jobId = `restore_legacy_lists_${Date.now()}`;
cronJobStorage.addToQueue(jobId, 'migration', step.weight, {
stepId: step.id,
stepName: step.name,
stepDescription: step.description
});
// Save initial job status
cronJobStorage.saveJobStatus(jobId, {
jobType: 'migration',
status: 'pending',
progress: 0,
stepId: step.id,
stepName: step.name
});
// Execute the migration immediately
const jobData = {
stepId: step.id,
stepName: step.name,
stepDescription: step.description
};
await this.executeMigrationJob(jobId, jobData);
return {
success: true,
jobId: jobId,
message: 'Restore legacy lists migration triggered successfully'
};
} catch (error) {
console.error('Error triggering restore legacy lists migration:', error);
throw new Meteor.Error('migration-trigger-failed', `Failed to trigger migration: ${error.message}`);
}
}
}
// Export singleton instance
@ -1666,17 +1508,4 @@ Meteor.methods({
return boardMigrationDetector.forceScan();
},
'cron.triggerRestoreLegacyLists'() {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
// Check if user is admin (optional - you can remove this if you want any user to trigger it)
const user = ReactiveCache.getCurrentUser();
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Only administrators can trigger this migration');
}
return cronMigrationManager.triggerRestoreLegacyListsMigration();
}
});