123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- /**
- * Board Conversion Service
- * Handles conversion of boards from old database structure to new structure
- * without running migrations that could cause downtime
- */
- import { ReactiveVar } from 'meteor/reactive-var';
- import { ReactiveCache } from '/imports/lib/reactiveCache';
- // Reactive variables for conversion progress
- export const conversionProgress = new ReactiveVar(0);
- export const conversionStatus = new ReactiveVar('');
- export const conversionEstimatedTime = new ReactiveVar('');
- export const isConverting = new ReactiveVar(false);
- class BoardConverter {
- constructor() {
- this.conversionCache = new Map(); // Cache converted board IDs
- }
- /**
- * Check if a board needs conversion
- * @param {string} boardId - The board ID to check
- * @returns {boolean} - True if board needs conversion
- */
- needsConversion(boardId) {
- if (this.conversionCache.has(boardId)) {
- return false; // Already converted
- }
- try {
- const board = ReactiveCache.getBoard(boardId);
- if (!board) return false;
- // Check if any lists in this board don't have swimlaneId
- const lists = ReactiveCache.getLists({
- boardId: boardId,
- $or: [
- { swimlaneId: { $exists: false } },
- { swimlaneId: '' },
- { swimlaneId: null }
- ]
- });
- return lists.length > 0;
- } catch (error) {
- console.error('Error checking if board needs conversion:', error);
- return false;
- }
- }
- /**
- * Convert a board from old structure to new structure
- * @param {string} boardId - The board ID to convert
- * @returns {Promise<boolean>} - True if conversion was successful
- */
- async convertBoard(boardId) {
- if (this.conversionCache.has(boardId)) {
- return true; // Already converted
- }
- isConverting.set(true);
- conversionProgress.set(0);
- conversionStatus.set('Starting board conversion...');
- try {
- const board = ReactiveCache.getBoard(boardId);
- if (!board) {
- throw new Error('Board not found');
- }
- // Get the default swimlane for this board
- const defaultSwimlane = board.getDefaultSwimline();
- if (!defaultSwimlane) {
- throw new Error('No default swimlane found for board');
- }
- // Get all lists that need conversion
- const listsToConvert = ReactiveCache.getLists({
- boardId: boardId,
- $or: [
- { swimlaneId: { $exists: false } },
- { swimlaneId: '' },
- { swimlaneId: null }
- ]
- });
- if (listsToConvert.length === 0) {
- this.conversionCache.set(boardId, true);
- isConverting.set(false);
- return true;
- }
- conversionStatus.set(`Converting ${listsToConvert.length} lists...`);
-
- const startTime = Date.now();
- const totalLists = listsToConvert.length;
- let convertedLists = 0;
- // Convert lists in batches to avoid blocking the UI
- const batchSize = 10;
- for (let i = 0; i < listsToConvert.length; i += batchSize) {
- const batch = listsToConvert.slice(i, i + batchSize);
-
- // Process batch
- await this.processBatch(batch, defaultSwimlane._id);
-
- convertedLists += batch.length;
- const progress = Math.round((convertedLists / totalLists) * 100);
- conversionProgress.set(progress);
-
- // Calculate estimated time remaining
- const elapsed = Date.now() - startTime;
- const rate = convertedLists / elapsed; // lists per millisecond
- const remaining = totalLists - convertedLists;
- const estimatedMs = remaining / rate;
-
- conversionStatus.set(`Converting list ${convertedLists} of ${totalLists}...`);
- conversionEstimatedTime.set(this.formatTime(estimatedMs));
- // Allow UI to update
- await new Promise(resolve => setTimeout(resolve, 10));
- }
- // Mark as converted
- this.conversionCache.set(boardId, true);
-
- conversionStatus.set('Board conversion completed!');
- conversionProgress.set(100);
-
- // Clear status after a delay
- setTimeout(() => {
- isConverting.set(false);
- conversionStatus.set('');
- conversionProgress.set(0);
- conversionEstimatedTime.set('');
- }, 2000);
- return true;
- } catch (error) {
- console.error('Error converting board:', error);
- conversionStatus.set(`Conversion failed: ${error.message}`);
- isConverting.set(false);
- return false;
- }
- }
- /**
- * Process a batch of lists for conversion
- * @param {Array} batch - Array of lists to convert
- * @param {string} defaultSwimlaneId - Default swimlane ID
- */
- async processBatch(batch, defaultSwimlaneId) {
- const updates = batch.map(list => ({
- _id: list._id,
- swimlaneId: defaultSwimlaneId
- }));
- // Update lists in batch
- updates.forEach(update => {
- ReactiveCache.getCollection('lists').update(update._id, {
- $set: { swimlaneId: update.swimlaneId }
- });
- });
- }
- /**
- * Format time in milliseconds to human readable format
- * @param {number} ms - Time in milliseconds
- * @returns {string} - Formatted time string
- */
- formatTime(ms) {
- if (ms < 1000) {
- return `${Math.round(ms)}ms`;
- }
- const seconds = Math.floor(ms / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- if (hours > 0) {
- const remainingMinutes = minutes % 60;
- const remainingSeconds = seconds % 60;
- return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
- } else if (minutes > 0) {
- const remainingSeconds = seconds % 60;
- return `${minutes}m ${remainingSeconds}s`;
- } else {
- return `${seconds}s`;
- }
- }
- /**
- * Clear conversion cache (useful for testing)
- */
- clearCache() {
- this.conversionCache.clear();
- }
- }
- // Export singleton instance
- export const boardConverter = new BoardConverter();
|