|
|
@@ -1,5 +1,6 @@
|
|
|
import { ReactiveCache } from '/imports/reactiveCache';
|
|
|
import { TAPi18n } from '/imports/i18n';
|
|
|
+import { migrationProgressManager } from '/client/components/migrationProgress';
|
|
|
|
|
|
BlazeComponent.extendComponent({
|
|
|
onCreated() {
|
|
|
@@ -29,11 +30,38 @@ BlazeComponent.extendComponent({
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ // Check delete duplicate empty lists migration
|
|
|
+ Meteor.call('deleteDuplicateEmptyLists.needsMigration', boardId, (err, res) => {
|
|
|
+ if (!err) {
|
|
|
+ const statuses = this.migrationStatuses.get();
|
|
|
+ statuses.deleteDuplicateEmptyLists = res;
|
|
|
+ this.migrationStatuses.set(statuses);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Check restore lost cards migration
|
|
|
+ Meteor.call('restoreLostCards.needsMigration', boardId, (err, res) => {
|
|
|
+ if (!err) {
|
|
|
+ const statuses = this.migrationStatuses.get();
|
|
|
+ statuses.restoreLostCards = res;
|
|
|
+ this.migrationStatuses.set(statuses);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Check restore all archived migration
|
|
|
+ Meteor.call('restoreAllArchived.needsMigration', boardId, (err, res) => {
|
|
|
+ if (!err) {
|
|
|
+ const statuses = this.migrationStatuses.get();
|
|
|
+ statuses.restoreAllArchived = res;
|
|
|
+ this.migrationStatuses.set(statuses);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
// Check fix avatar URLs migration (global)
|
|
|
- Meteor.call('fixAvatarUrls.needsMigration', (err, res) => {
|
|
|
+ Meteor.call('fixAvatarUrls.needsMigration', (err, res) => {
|
|
|
if (!err) {
|
|
|
const statuses = this.migrationStatuses.get();
|
|
|
- statuses.fixAvatarUrls = res;
|
|
|
+ statuses.fixAvatarUrls = res;
|
|
|
this.migrationStatuses.set(statuses);
|
|
|
}
|
|
|
});
|
|
|
@@ -56,6 +84,22 @@ BlazeComponent.extendComponent({
|
|
|
return this.migrationStatuses.get().fixMissingLists === true;
|
|
|
},
|
|
|
|
|
|
+ deleteEmptyListsNeeded() {
|
|
|
+ return this.migrationStatuses.get().deleteEmptyLists === true;
|
|
|
+ },
|
|
|
+
|
|
|
+ deleteDuplicateEmptyListsNeeded() {
|
|
|
+ return this.migrationStatuses.get().deleteDuplicateEmptyLists === true;
|
|
|
+ },
|
|
|
+
|
|
|
+ restoreLostCardsNeeded() {
|
|
|
+ return this.migrationStatuses.get().restoreLostCards === true;
|
|
|
+ },
|
|
|
+
|
|
|
+ restoreAllArchivedNeeded() {
|
|
|
+ return this.migrationStatuses.get().restoreAllArchived === true;
|
|
|
+ },
|
|
|
+
|
|
|
fixAvatarUrlsNeeded() {
|
|
|
return this.migrationStatuses.get().fixAvatarUrls === true;
|
|
|
},
|
|
|
@@ -64,6 +108,58 @@ BlazeComponent.extendComponent({
|
|
|
return this.migrationStatuses.get().fixAllFileUrls === true;
|
|
|
},
|
|
|
|
|
|
+ // Simulate migration progress updates using the global progress popup
|
|
|
+ async simulateMigrationProgress(progressSteps) {
|
|
|
+ const totalSteps = progressSteps.length;
|
|
|
+ for (let i = 0; i < progressSteps.length; i++) {
|
|
|
+ const step = progressSteps[i];
|
|
|
+ const overall = Math.round(((i + 1) / totalSteps) * 100);
|
|
|
+
|
|
|
+ // Start step
|
|
|
+ migrationProgressManager.updateProgress({
|
|
|
+ overallProgress: overall,
|
|
|
+ currentStep: i + 1,
|
|
|
+ totalSteps,
|
|
|
+ stepName: step.step,
|
|
|
+ stepProgress: 0,
|
|
|
+ stepStatus: `Starting ${step.name}...`,
|
|
|
+ stepDetails: null,
|
|
|
+ boardId: Session.get('currentBoard'),
|
|
|
+ });
|
|
|
+
|
|
|
+ const stepDuration = step.duration;
|
|
|
+ const updateInterval = 100;
|
|
|
+ const totalUpdates = Math.max(1, Math.floor(stepDuration / updateInterval));
|
|
|
+ for (let j = 0; j < totalUpdates; j++) {
|
|
|
+ const per = Math.round(((j + 1) / totalUpdates) * 100);
|
|
|
+ migrationProgressManager.updateProgress({
|
|
|
+ overallProgress: overall,
|
|
|
+ currentStep: i + 1,
|
|
|
+ totalSteps,
|
|
|
+ stepName: step.step,
|
|
|
+ stepProgress: per,
|
|
|
+ stepStatus: `Processing ${step.name}...`,
|
|
|
+ stepDetails: { progress: `${per}%` },
|
|
|
+ boardId: Session.get('currentBoard'),
|
|
|
+ });
|
|
|
+ // eslint-disable-next-line no-await-in-loop
|
|
|
+ await new Promise((r) => setTimeout(r, updateInterval));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Complete step
|
|
|
+ migrationProgressManager.updateProgress({
|
|
|
+ overallProgress: overall,
|
|
|
+ currentStep: i + 1,
|
|
|
+ totalSteps,
|
|
|
+ stepName: step.step,
|
|
|
+ stepProgress: 100,
|
|
|
+ stepStatus: `${step.name} completed`,
|
|
|
+ stepDetails: { status: 'completed' },
|
|
|
+ boardId: Session.get('currentBoard'),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
runMigration(migrationType) {
|
|
|
const boardId = Session.get('currentBoard');
|
|
|
|
|
|
@@ -81,6 +177,26 @@ BlazeComponent.extendComponent({
|
|
|
methodArgs = [boardId];
|
|
|
break;
|
|
|
|
|
|
+ case 'deleteEmptyLists':
|
|
|
+ methodName = 'deleteEmptyLists.execute';
|
|
|
+ methodArgs = [boardId];
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'deleteDuplicateEmptyLists':
|
|
|
+ methodName = 'deleteDuplicateEmptyLists.execute';
|
|
|
+ methodArgs = [boardId];
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'restoreLostCards':
|
|
|
+ methodName = 'restoreLostCards.execute';
|
|
|
+ methodArgs = [boardId];
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'restoreAllArchived':
|
|
|
+ methodName = 'restoreAllArchived.execute';
|
|
|
+ methodArgs = [boardId];
|
|
|
+ break;
|
|
|
+
|
|
|
case 'fixAvatarUrls':
|
|
|
methodName = 'fixAvatarUrls.execute';
|
|
|
break;
|
|
|
@@ -91,17 +207,104 @@ BlazeComponent.extendComponent({
|
|
|
}
|
|
|
|
|
|
if (methodName) {
|
|
|
- Meteor.call(methodName, ...methodArgs, (err, result) => {
|
|
|
- if (err) {
|
|
|
- console.error('Migration failed:', err);
|
|
|
- // Show error notification
|
|
|
- Alert.error(TAPi18n.__('migration-failed') + ': ' + (err.message || err.reason));
|
|
|
+ // Define simulated steps per migration type
|
|
|
+ const stepsByType = {
|
|
|
+ comprehensive: [
|
|
|
+ { step: 'analyze_board_structure', name: 'Analyze Board Structure', duration: 800 },
|
|
|
+ { step: 'fix_orphaned_cards', name: 'Fix Orphaned Cards', duration: 1200 },
|
|
|
+ { step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 1000 },
|
|
|
+ { step: 'ensure_per_swimlane_lists', name: 'Ensure Per-Swimlane Lists', duration: 800 },
|
|
|
+ { step: 'validate_migration', name: 'Validate Migration', duration: 800 },
|
|
|
+ { step: 'fix_avatar_urls', name: 'Fix Avatar URLs', duration: 600 },
|
|
|
+ { step: 'fix_attachment_urls', name: 'Fix Attachment URLs', duration: 600 },
|
|
|
+ ],
|
|
|
+ fixMissingLists: [
|
|
|
+ { step: 'analyze_lists', name: 'Analyze Lists', duration: 600 },
|
|
|
+ { step: 'create_missing_lists', name: 'Create Missing Lists', duration: 900 },
|
|
|
+ { step: 'update_cards', name: 'Update Cards', duration: 900 },
|
|
|
+ { step: 'finalize', name: 'Finalize', duration: 400 },
|
|
|
+ ],
|
|
|
+ deleteEmptyLists: [
|
|
|
+ { step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 700 },
|
|
|
+ { step: 'delete_empty_lists', name: 'Delete Empty Lists', duration: 800 },
|
|
|
+ ],
|
|
|
+ deleteDuplicateEmptyLists: [
|
|
|
+ { step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 700 },
|
|
|
+ { step: 'delete_duplicate_empty_lists', name: 'Delete Duplicate Empty Lists', duration: 800 },
|
|
|
+ ],
|
|
|
+ restoreLostCards: [
|
|
|
+ { step: 'ensure_lost_cards_swimlane', name: 'Ensure Lost Cards Swimlane', duration: 600 },
|
|
|
+ { step: 'restore_lists', name: 'Restore Lists', duration: 800 },
|
|
|
+ { step: 'restore_cards', name: 'Restore Cards', duration: 1000 },
|
|
|
+ ],
|
|
|
+ restoreAllArchived: [
|
|
|
+ { step: 'restore_swimlanes', name: 'Restore Swimlanes', duration: 800 },
|
|
|
+ { step: 'restore_lists', name: 'Restore Lists', duration: 900 },
|
|
|
+ { step: 'restore_cards', name: 'Restore Cards', duration: 1000 },
|
|
|
+ { step: 'fix_missing_ids', name: 'Fix Missing IDs', duration: 600 },
|
|
|
+ ],
|
|
|
+ fixAvatarUrls: [
|
|
|
+ { step: 'scan_users', name: 'Scan Users', duration: 500 },
|
|
|
+ { step: 'fix_urls', name: 'Fix Avatar URLs', duration: 900 },
|
|
|
+ ],
|
|
|
+ fixAllFileUrls: [
|
|
|
+ { step: 'scan_files', name: 'Scan Files', duration: 600 },
|
|
|
+ { step: 'fix_urls', name: 'Fix File URLs', duration: 1000 },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+
|
|
|
+ const steps = stepsByType[migrationType] || [
|
|
|
+ { step: 'running', name: 'Running Migration', duration: 1000 },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // Kick off popup and simulated progress
|
|
|
+ migrationProgressManager.startMigration();
|
|
|
+ const progressPromise = this.simulateMigrationProgress(steps);
|
|
|
+
|
|
|
+ // Start migration call
|
|
|
+ const callPromise = new Promise((resolve, reject) => {
|
|
|
+ Meteor.call(methodName, ...methodArgs, (err, result) => {
|
|
|
+ if (err) return reject(err);
|
|
|
+ return resolve(result);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ Promise.allSettled([callPromise, progressPromise]).then(([callRes]) => {
|
|
|
+ if (callRes.status === 'rejected') {
|
|
|
+ migrationProgressManager.failMigration(callRes.reason);
|
|
|
} else {
|
|
|
- console.log('Migration completed:', result);
|
|
|
- // Show success notification
|
|
|
- Alert.success(TAPi18n.__('migration-successful'));
|
|
|
-
|
|
|
- // Reload migration statuses
|
|
|
+ const result = callRes.value;
|
|
|
+ // Summarize result details in the popup
|
|
|
+ let summary = {};
|
|
|
+ if (result && result.results) {
|
|
|
+ // Comprehensive returns {success, results}
|
|
|
+ const r = result.results;
|
|
|
+ summary = {
|
|
|
+ totalCardsProcessed: r.totalCardsProcessed,
|
|
|
+ totalListsProcessed: r.totalListsProcessed,
|
|
|
+ totalListsCreated: r.totalListsCreated,
|
|
|
+ };
|
|
|
+ } else if (result && result.changes) {
|
|
|
+ // Many migrations return a changes string array
|
|
|
+ summary = { changes: result.changes.join(' | ') };
|
|
|
+ } else if (result && typeof result === 'object') {
|
|
|
+ summary = result;
|
|
|
+ }
|
|
|
+
|
|
|
+ migrationProgressManager.updateProgress({
|
|
|
+ overallProgress: 100,
|
|
|
+ currentStep: steps.length,
|
|
|
+ totalSteps: steps.length,
|
|
|
+ stepName: 'completed',
|
|
|
+ stepProgress: 100,
|
|
|
+ stepStatus: 'Migration completed',
|
|
|
+ stepDetails: summary,
|
|
|
+ boardId: Session.get('currentBoard'),
|
|
|
+ });
|
|
|
+
|
|
|
+ migrationProgressManager.completeMigration();
|
|
|
+
|
|
|
+ // Refresh status badges slightly after
|
|
|
Meteor.setTimeout(() => {
|
|
|
this.loadMigrationStatuses();
|
|
|
}, 1000);
|
|
|
@@ -111,31 +314,41 @@ BlazeComponent.extendComponent({
|
|
|
},
|
|
|
|
|
|
events() {
|
|
|
+ const self = this; // Capture component reference
|
|
|
+
|
|
|
return [
|
|
|
{
|
|
|
'click .js-run-migration[data-migration="comprehensive"]': Popup.afterConfirm('runComprehensiveMigration', function() {
|
|
|
- const component = BlazeComponent.getComponentForElement(this);
|
|
|
- if (component) {
|
|
|
- component.runMigration('comprehensive');
|
|
|
- }
|
|
|
+ self.runMigration('comprehensive');
|
|
|
+ Popup.back();
|
|
|
}),
|
|
|
'click .js-run-migration[data-migration="fixMissingLists"]': Popup.afterConfirm('runFixMissingListsMigration', function() {
|
|
|
- const component = BlazeComponent.getComponentForElement(this);
|
|
|
- if (component) {
|
|
|
- component.runMigration('fixMissingLists');
|
|
|
- }
|
|
|
+ self.runMigration('fixMissingLists');
|
|
|
+ Popup.back();
|
|
|
+ }),
|
|
|
+ 'click .js-run-migration[data-migration="deleteEmptyLists"]': Popup.afterConfirm('runDeleteEmptyListsMigration', function() {
|
|
|
+ self.runMigration('deleteEmptyLists');
|
|
|
+ Popup.back();
|
|
|
+ }),
|
|
|
+ 'click .js-run-migration[data-migration="deleteDuplicateEmptyLists"]': Popup.afterConfirm('runDeleteDuplicateEmptyListsMigration', function() {
|
|
|
+ self.runMigration('deleteDuplicateEmptyLists');
|
|
|
+ Popup.back();
|
|
|
+ }),
|
|
|
+ 'click .js-run-migration[data-migration="restoreLostCards"]': Popup.afterConfirm('runRestoreLostCardsMigration', function() {
|
|
|
+ self.runMigration('restoreLostCards');
|
|
|
+ Popup.back();
|
|
|
+ }),
|
|
|
+ 'click .js-run-migration[data-migration="restoreAllArchived"]': Popup.afterConfirm('runRestoreAllArchivedMigration', function() {
|
|
|
+ self.runMigration('restoreAllArchived');
|
|
|
+ Popup.back();
|
|
|
}),
|
|
|
'click .js-run-migration[data-migration="fixAvatarUrls"]': Popup.afterConfirm('runFixAvatarUrlsMigration', function() {
|
|
|
- const component = BlazeComponent.getComponentForElement(this);
|
|
|
- if (component) {
|
|
|
- component.runMigration('fixAvatarUrls');
|
|
|
- }
|
|
|
+ self.runMigration('fixAvatarUrls');
|
|
|
+ Popup.back();
|
|
|
}),
|
|
|
'click .js-run-migration[data-migration="fixAllFileUrls"]': Popup.afterConfirm('runFixAllFileUrlsMigration', function() {
|
|
|
- const component = BlazeComponent.getComponentForElement(this);
|
|
|
- if (component) {
|
|
|
- component.runMigration('fixAllFileUrls');
|
|
|
- }
|
|
|
+ self.runMigration('fixAllFileUrls');
|
|
|
+ Popup.back();
|
|
|
}),
|
|
|
},
|
|
|
];
|