Moved migrations from opening board to right sidebar / Migrations.

Thanks to xet7 !
This commit is contained in:
Lauri Ojansivu 2025-11-05 17:06:26 +02:00
parent e93e72234c
commit 1b25d1d572
7 changed files with 277 additions and 62 deletions

View file

@ -99,24 +99,9 @@ BlazeComponent.extendComponent({
return;
}
// Check if board needs comprehensive migration
const needsMigration = await this.checkComprehensiveMigration(boardId);
if (needsMigration) {
// Start comprehensive migration
this.isMigrating.set(true);
const success = await this.executeComprehensiveMigration(boardId);
this.isMigrating.set(false);
if (success) {
this.isBoardReady.set(true);
} else {
console.error('Comprehensive migration failed, setting ready to true anyway');
this.isBoardReady.set(true); // Still show board even if migration failed
}
} else {
this.isBoardReady.set(true);
}
// Automatic migration disabled - migrations must be run manually from sidebar
// Board admins can run migrations from the sidebar Migrations menu
this.isBoardReady.set(true);
} catch (error) {
console.error('Error during board conversion check:', error);

View file

@ -587,6 +587,10 @@ template(name="boardMenuPopup")
| 📦
| {{_ 'archived-items'}}
if currentUser.isBoardAdmin
li
a.js-open-migrations
| 🔧
| {{_ 'migrations'}}
li
a.js-change-board-color
| 🎨

View file

@ -13,6 +13,7 @@ const viewTitles = {
multiselection: 'multi-selection',
customFields: 'custom-fields',
archives: 'archives',
migrations: 'migrations',
};
BlazeComponent.extendComponent({
@ -271,6 +272,10 @@ Template.boardMenuPopup.events({
Sidebar.setView('archives');
Popup.back();
},
'click .js-open-migrations'() {
Sidebar.setView('migrations');
Popup.back();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),

View file

@ -0,0 +1,69 @@
template(name='migrationsSidebar')
if currentUser.isBoardAdmin
.sidebar-migrations
h3
| 🔧
| {{_ 'migrations'}}
p.quiet {{_ 'migrations-description'}}
.migrations-list
h4 {{_ 'board-migrations'}}
.migration-item
a.js-run-migration(data-migration="comprehensive")
.migration-name
| {{_ 'comprehensive-board-migration'}}
.migration-status
if comprehensiveMigrationNeeded
span.badge.badge-warning {{_ 'migration-needed'}}
else
span.badge.badge-success {{_ 'migration-complete'}}
.migration-item
a.js-run-migration(data-migration="fixMissingLists")
.migration-name
| {{_ 'fix-missing-lists-migration'}}
.migration-status
if fixMissingListsNeeded
span.badge.badge-warning {{_ 'migration-needed'}}
else
span.badge.badge-success {{_ 'migration-complete'}}
hr
h4 {{_ 'global-migrations'}}
.migration-item
a.js-run-migration(data-migration="fixAvatarUrls")
.migration-name
| {{_ 'fix-avatar-urls-migration'}}
.migration-status
if fixAvatarUrlsNeeded
span.badge.badge-warning {{_ 'migration-needed'}}
else
span.badge.badge-success {{_ 'migration-complete'}}
.migration-item
a.js-run-migration(data-migration="fixAllFileUrls")
.migration-name
| {{_ 'fix-all-file-urls-migration'}}
.migration-status
if fixAllFileUrlsNeeded
span.badge.badge-warning {{_ 'migration-needed'}}
else
span.badge.badge-success {{_ 'migration-complete'}}
else
p.quiet {{_ 'migrations-admin-only'}}
template(name='runComprehensiveMigrationPopup')
p {{_ 'run-comprehensive-migration-confirm'}}
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
template(name='runFixMissingListsMigrationPopup')
p {{_ 'run-fix-missing-lists-migration-confirm'}}
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
template(name='runFixAvatarUrlsMigrationPopup')
p {{_ 'run-fix-avatar-urls-migration-confirm'}}
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
template(name='runFixAllFileUrlsMigrationPopup')
p {{_ 'run-fix-all-file-urls-migration-confirm'}}
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}

View file

@ -0,0 +1,143 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
BlazeComponent.extendComponent({
onCreated() {
this.migrationStatuses = new ReactiveVar({});
this.loadMigrationStatuses();
},
loadMigrationStatuses() {
const boardId = Session.get('currentBoard');
if (!boardId) return;
// Check comprehensive migration
Meteor.call('comprehensiveBoardMigration.needsMigration', boardId, (err, res) => {
if (!err) {
const statuses = this.migrationStatuses.get();
statuses.comprehensive = res;
this.migrationStatuses.set(statuses);
}
});
// Check fix missing lists migration
Meteor.call('fixMissingListsMigration.needsMigration', boardId, (err, res) => {
if (!err) {
const statuses = this.migrationStatuses.get();
statuses.fixMissingLists = res;
this.migrationStatuses.set(statuses);
}
});
// Check fix avatar URLs migration (global)
Meteor.call('fixAvatarUrls.needsMigration', (err, res) => {
if (!err) {
const statuses = this.migrationStatuses.get();
statuses.fixAvatarUrls = res;
this.migrationStatuses.set(statuses);
}
});
// Check fix all file URLs migration (global)
Meteor.call('fixAllFileUrls.needsMigration', (err, res) => {
if (!err) {
const statuses = this.migrationStatuses.get();
statuses.fixAllFileUrls = res;
this.migrationStatuses.set(statuses);
}
});
},
comprehensiveMigrationNeeded() {
return this.migrationStatuses.get().comprehensive === true;
},
fixMissingListsNeeded() {
return this.migrationStatuses.get().fixMissingLists === true;
},
fixAvatarUrlsNeeded() {
return this.migrationStatuses.get().fixAvatarUrls === true;
},
fixAllFileUrlsNeeded() {
return this.migrationStatuses.get().fixAllFileUrls === true;
},
runMigration(migrationType) {
const boardId = Session.get('currentBoard');
let methodName;
let methodArgs = [];
switch (migrationType) {
case 'comprehensive':
methodName = 'comprehensiveBoardMigration.execute';
methodArgs = [boardId];
break;
case 'fixMissingLists':
methodName = 'fixMissingListsMigration.execute';
methodArgs = [boardId];
break;
case 'fixAvatarUrls':
methodName = 'fixAvatarUrls.execute';
break;
case 'fixAllFileUrls':
methodName = 'fixAllFileUrls.execute';
break;
}
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));
} else {
console.log('Migration completed:', result);
// Show success notification
Alert.success(TAPi18n.__('migration-successful'));
// Reload migration statuses
Meteor.setTimeout(() => {
this.loadMigrationStatuses();
}, 1000);
}
});
}
},
events() {
return [
{
'click .js-run-migration[data-migration="comprehensive"]': Popup.afterConfirm('runComprehensiveMigration', function() {
const component = BlazeComponent.getComponentForElement(this);
if (component) {
component.runMigration('comprehensive');
}
}),
'click .js-run-migration[data-migration="fixMissingLists"]': Popup.afterConfirm('runFixMissingListsMigration', function() {
const component = BlazeComponent.getComponentForElement(this);
if (component) {
component.runMigration('fixMissingLists');
}
}),
'click .js-run-migration[data-migration="fixAvatarUrls"]': Popup.afterConfirm('runFixAvatarUrlsMigration', function() {
const component = BlazeComponent.getComponentForElement(this);
if (component) {
component.runMigration('fixAvatarUrls');
}
}),
'click .js-run-migration[data-migration="fixAllFileUrls"]': Popup.afterConfirm('runFixAllFileUrlsMigration', function() {
const component = BlazeComponent.getComponentForElement(this);
if (component) {
component.runMigration('fixAllFileUrls');
}
}),
},
];
},
}).register('migrationsSidebar');

View file

@ -1404,7 +1404,31 @@
"back-to-settings": "Back to Settings",
"board-id": "Board ID",
"board-migration": "Board Migration",
"board-migrations": "Board Migrations",
"card-show-lists-on-minicard": "Show Lists on Minicard",
"comprehensive-board-migration": "Comprehensive Board Migration",
"comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.",
"fix-missing-lists-migration": "Fix Missing Lists",
"fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.",
"fix-avatar-urls-migration": "Fix Avatar URLs",
"fix-avatar-urls-migration-description": "Updates avatar URLs to use the correct storage backend and fixes broken avatar references.",
"fix-all-file-urls-migration": "Fix All File URLs",
"fix-all-file-urls-migration-description": "Updates all file attachment URLs to use the correct storage backend and fixes broken file references.",
"global-migrations": "Global Migrations",
"migration-needed": "Migration Needed",
"migration-complete": "Complete",
"migration-running": "Running...",
"migration-successful": "Migration completed successfully",
"migration-failed": "Migration failed",
"migrations": "Migrations",
"migrations-admin-only": "Only board administrators can run migrations",
"migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.",
"no-issues-found": "No issues found",
"run-migration": "Run Migration",
"run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?",
"run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?",
"run-fix-avatar-urls-migration-confirm": "This will update avatar URLs across all boards to use the correct storage backend. This is a global operation. Continue?",
"run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs across all boards to use the correct storage backend. This is a global operation. Continue?",
"cleanup": "Cleanup",
"cleanup-old-jobs": "Cleanup Old Jobs",
"completed": "Completed",

View file

@ -30,15 +30,11 @@ class FixAllFileUrlsMigration {
}
}
// Check for problematic attachment URLs in cards
const cards = ReactiveCache.getCards({});
for (const card of cards) {
if (card.attachments) {
for (const attachment of card.attachments) {
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
return true;
}
}
// Check for problematic attachment URLs
const attachments = ReactiveCache.getAttachments({});
for (const attachment of attachments) {
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
return true;
}
}
@ -206,51 +202,40 @@ class FixAllFileUrlsMigration {
}
/**
* Fix attachment URLs in card references
* Fix attachment URLs in the Attachments collection
*/
async fixCardAttachmentUrls() {
const cards = ReactiveCache.getCards({});
let cardsFixed = 0;
const attachments = ReactiveCache.getAttachments({});
let attachmentsFixed = 0;
for (const card of cards) {
if (card.attachments) {
let needsUpdate = false;
const updatedAttachments = card.attachments.map(attachment => {
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
try {
const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment');
const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment');
for (const attachment of attachments) {
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
try {
const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment');
const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment');
if (cleanUrl && cleanUrl !== attachment.url) {
needsUpdate = true;
return { ...attachment, url: cleanUrl };
if (cleanUrl && cleanUrl !== attachment.url) {
// Update attachment with fixed URL
Attachments.update(attachment._id, {
$set: {
url: cleanUrl,
modifiedAt: new Date()
}
} catch (error) {
console.error(`Error fixing card attachment URL:`, error);
});
attachmentsFixed++;
if (process.env.DEBUG === 'true') {
console.log(`Fixed attachment URL ${attachment._id}`);
}
}
return attachment;
});
if (needsUpdate) {
// Update card with fixed attachment URLs
Cards.update(card._id, {
$set: {
attachments: updatedAttachments,
modifiedAt: new Date()
}
});
cardsFixed++;
if (process.env.DEBUG === 'true') {
console.log(`Fixed attachment URLs in card ${card._id}`);
}
} catch (error) {
console.error(`Error fixing attachment URL:`, error);
}
}
}
return cardsFixed;
return attachmentsFixed;
}
}