mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
Legacy Lists button at one board view to restore missing lists/cards.
Thanks to xet7 ! Fixes #5952
This commit is contained in:
parent
1658883b78
commit
951d2e4937
5 changed files with 233 additions and 14 deletions
|
|
@ -130,6 +130,11 @@ template(name="boardHeaderBar")
|
|||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||
| ❌
|
||||
|
||||
if currentUser.isBoardAdmin
|
||||
a.board-header-btn.js-restore-legacy-lists(title="{{_ 'restore-legacy-lists'}}")
|
||||
| 🔄
|
||||
| {{_ 'legacy-lists'}}
|
||||
|
||||
.separator
|
||||
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")
|
||||
| ☰
|
||||
|
|
|
|||
|
|
@ -152,9 +152,32 @@ BlazeComponent.extendComponent({
|
|||
'click .js-log-in'() {
|
||||
FlowRouter.go('atSignIn');
|
||||
},
|
||||
'click .js-restore-legacy-lists'() {
|
||||
this.restoreLegacyLists();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
restoreLegacyLists() {
|
||||
// Show confirmation dialog
|
||||
if (confirm('Are you sure you want to restore legacy lists to their original shared state? This will make them appear in all swimlanes.')) {
|
||||
// Call cron method to restore legacy lists
|
||||
Meteor.call('cron.triggerRestoreLegacyLists', (error, result) => {
|
||||
if (error) {
|
||||
console.error('Error restoring legacy lists:', error);
|
||||
alert(`Error: ${error.message}`);
|
||||
} else {
|
||||
console.log('Successfully triggered restore legacy lists migration:', result);
|
||||
alert(`Migration triggered successfully. Job ID: ${result.jobId}`);
|
||||
// Refresh the board to show the restored lists
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
}).register('boardHeaderBar');
|
||||
|
||||
Template.boardHeaderBar.helpers({
|
||||
|
|
|
|||
|
|
@ -1488,5 +1488,10 @@
|
|||
"weight": "Weight",
|
||||
"idle": "Idle",
|
||||
"complete": "Complete",
|
||||
"cron": "Cron"
|
||||
"cron": "Cron",
|
||||
"legacy-lists": "Legacy Lists",
|
||||
"restore-legacy-lists": "Restore Legacy Lists",
|
||||
"legacy-lists-restore": "Legacy Lists Restore",
|
||||
"legacy-lists-restore-description": "Restore legacy lists to their original shared state across all swimlanes",
|
||||
"restoring-legacy-lists": "Restoring legacy lists"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -778,6 +778,10 @@ Boards.helpers({
|
|||
return this.permission === 'public';
|
||||
},
|
||||
|
||||
hasLegacyLists() {
|
||||
return this.hasLegacyLists === true;
|
||||
},
|
||||
|
||||
cards() {
|
||||
const ret = ReactiveCache.getCards(
|
||||
{ boardId: this._id, archived: false },
|
||||
|
|
|
|||
|
|
@ -232,6 +232,17 @@ class CronMigrationManager {
|
|||
cronName: 'migration_lists_per_swimlane',
|
||||
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'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -357,9 +368,16 @@ class CronMigrationManager {
|
|||
* Execute a migration job
|
||||
*/
|
||||
async executeMigrationJob(jobId, jobData) {
|
||||
const { stepId } = jobData;
|
||||
const step = this.migrationSteps.find(s => s.id === stepId);
|
||||
if (!jobData) {
|
||||
throw new Error('Job data is required for migration execution');
|
||||
}
|
||||
|
||||
const { stepId } = jobData;
|
||||
if (!stepId) {
|
||||
throw new Error('Step ID is required in job data');
|
||||
}
|
||||
|
||||
const step = this.migrationSteps.find(s => s.id === stepId);
|
||||
if (!step) {
|
||||
throw new Error(`Migration step ${stepId} not found`);
|
||||
}
|
||||
|
|
@ -378,7 +396,7 @@ class CronMigrationManager {
|
|||
});
|
||||
|
||||
// Execute step
|
||||
await this.executeMigrationStep(jobId, i, stepData);
|
||||
await this.executeMigrationStep(jobId, i, stepData, stepId);
|
||||
|
||||
// Mark step as completed
|
||||
cronJobStorage.saveJobStep(jobId, i, {
|
||||
|
|
@ -423,6 +441,14 @@ 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 },
|
||||
|
|
@ -436,10 +462,13 @@ class CronMigrationManager {
|
|||
/**
|
||||
* Execute a migration step
|
||||
*/
|
||||
async executeMigrationStep(jobId, stepIndex, stepData) {
|
||||
async executeMigrationStep(jobId, stepIndex, stepData, stepId) {
|
||||
const { name, duration } = stepData;
|
||||
|
||||
// Simulate step execution with progress updates
|
||||
if (stepId === 'restore-legacy-lists') {
|
||||
await this.executeRestoreLegacyListsMigration(jobId, stepIndex, stepData);
|
||||
} else {
|
||||
// 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);
|
||||
|
|
@ -454,6 +483,97 @@ class CronMigrationManager {
|
|||
await new Promise(resolve => setTimeout(resolve, duration / progressSteps));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -1299,6 +1419,54 @@ 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
|
||||
|
|
@ -1496,5 +1664,19 @@ Meteor.methods({
|
|||
// Import the board migration detector
|
||||
const { boardMigrationDetector } = require('./boardMigrationDetector');
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue