Skip to content

Commit b0643aa

Browse files
committed
✨ Enhanced rollback progress tracking with step-by-step indicators - Added ProgressTracker integration to rollback service - Implemented step activation logic with startStep() calls - Enhanced frontend progress UI with pink pulsing animations - Fixed TypeScript compilation errors for deployment - Added comprehensive rollback documentation to DEVELOPER_ARCHITECTURE.md - Updated USAGE-GUIDE.md with enhanced progress tracking features - All 669 unit tests passing
1 parent 4400cee commit b0643aa

File tree

18 files changed

+673
-259
lines changed

18 files changed

+673
-259
lines changed

docs/DEVELOPER_ARCHITECTURE.md

Lines changed: 209 additions & 129 deletions
Large diffs are not rendered by default.

docs/USAGE-GUIDE.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ The application provides a **modern React wizard interface** with Fluent UI v9 c
8585
- **Real-time Validation**: Final validation before deployment
8686
- **Deploy**: Select **Deploy to Dataverse** to create your solution with live progress tracking
8787

88-
![Deployment progress](media/step-4-deploying.png)
88+
![Deployment progress](media/step-4-progress-indicator.png)
8989

9090
And finally
9191

@@ -322,6 +322,30 @@ The application provides **modular rollback** functionality, giving you granular
322322

323323
![complete rollback](media/rollback-complete.png)
324324

325+
### Enhanced Progress Tracking
326+
327+
The rollback process includes **real-time progress indicators** with step-by-step visibility:
328+
329+
**Visual Progress Features:**
330+
- **Step-by-Step Display**: Each rollback component shown as individual progress steps
331+
- **Active Step Highlighting**: Currently processing step highlighted with pink pulsing animation
332+
- **Status Badges**: Clear "Preparing", "In Progress", "Completed" indicators for each step
333+
- **Time Estimation**: Real-time elapsed time and estimated remaining time
334+
- **Spinner Animations**: Visual feedback for active operations
335+
336+
**Progress Steps Include:**
337+
1. **Preparation** - Validating rollback requirements
338+
2. **Relationships** - Removing entity relationships
339+
3. **Custom Entities** - Deleting custom tables
340+
4. **Global Choices** - Removing custom choice sets
341+
5. **Solution** - Deleting solution container
342+
6. **Publisher** - Removing publisher
343+
7. **Cleanup** - Final cleanup operations
344+
345+
This enhanced progress tracking matches the same visual style and functionality as deployment progress, providing consistent user experience throughout the application.
346+
347+
![rollback progress indicator](/rollback-progress-indicator.png)
348+
325349
### Rollback Options
326350

327351
You can choose exactly what to rollback:
178 KB
Loading
65.3 KB
Loading

rollback-test.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"confirm": true,
3+
"options": {
4+
"relationships": true,
5+
"customEntities": false,
6+
"customGlobalChoices": false,
7+
"solution": false,
8+
"publisher": false
9+
}
10+
}

src/backend/controllers/rollback-controller.js

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,27 +119,23 @@ class RollbackController {
119119
// Update status to in-progress
120120
this.statusTracker.updateStatus(rollbackId, 'in-progress');
121121

122-
// Track progress phases
123-
const phases = ['relationships', 'entities', 'globalChoices', 'solution', 'publisher'];
124-
let currentPhaseIndex = 0;
125-
126122
// Progress callback to update tracker
127-
// Receives (status, message) where status is the phase name
128-
const progressCallback = (status, message) => {
129-
console.log(`Rollback ${rollbackId}: ${status} - ${message}`);
123+
// Updated to handle new format: (type, message, progressData)
124+
const progressCallback = (type, message, progressData) => {
125+
console.log(`Rollback ${rollbackId}: ${type} - ${message}`);
130126

131-
// Update current phase
132-
const phaseIndex = phases.indexOf(status);
133-
if (phaseIndex >= 0) {
134-
currentPhaseIndex = phaseIndex + 1;
127+
if (type === 'progress' && progressData) {
128+
// Extract progress information from progressData
129+
const percentage = progressData.percentage || 0;
130+
const total = progressData.steps ? progressData.steps.length : 100;
131+
const current = Math.round((percentage / 100) * total);
132+
133+
// Update progress with enhanced data for the frontend
134+
this.statusTracker.updateProgressWithData(rollbackId, current, total, message, progressData);
135+
} else {
136+
// Fallback for any unexpected format
137+
console.log(`Unexpected progress format: ${type}, ${message}`);
135138
}
136-
137-
// Calculate progress (each phase is a step)
138-
const total = phases.length;
139-
const current = currentPhaseIndex;
140-
141-
// Just pass the message without phase numbers
142-
this.statusTracker.updateProgress(rollbackId, current, total, message);
143139
};
144140

145141
// Execute the rollback with progress tracking

src/backend/dataverse-client.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,14 @@ class DataverseClient {
338338
} catch (error) {
339339
const isRetryable = retryableStatusCodes.includes(error.status);
340340
const isLastAttempt = attempt === maxRetries;
341+
342+
// Check for EntityCustomization conflict error
343+
const isEntityCustomizationConflict = error.message &&
344+
(error.message.includes('0x80071151') ||
345+
error.message.includes('EntityCustomization') ||
346+
error.message.includes('solution installation or removal failed'));
341347

342-
if (!isRetryable || isLastAttempt) {
348+
if ((!isRetryable && !isEntityCustomizationConflict) || isLastAttempt) {
343349
// Not retryable or out of retries, throw the error
344350
throw error;
345351
}
@@ -357,7 +363,12 @@ class DataverseClient {
357363

358364
// Handle rate limiting with Retry-After header
359365
let delayMs = retryDelays[attempt];
360-
if (error.status === 429) {
366+
367+
// Special handling for EntityCustomization conflicts - use longer delays
368+
if (isEntityCustomizationConflict) {
369+
delayMs = Math.max(delayMs, 10000 + (attempt * 5000)); // Minimum 10s, increasing by 5s each attempt
370+
this._log(`⚠️ EntityCustomization conflict detected. Retrying in ${delayMs / 1000} seconds (attempt ${attempt + 1}/${maxRetries + 1})...`);
371+
} else if (error.status === 429) {
361372
// Check for Retry-After header (in seconds)
362373
const retryAfter = error.response?.headers?.['retry-after'];
363374
if (retryAfter) {

0 commit comments

Comments
 (0)