function TxnProgressWidget( txnProgressContext, txnProgressStatusMessage, txnProgressPostCompletionFixup ) { /**************************** Private methods ********************************/ var txnProgressStateShouldBeSkipped = function( txnProgressState ) { var skipIt = false; /* Step over any deploy progress states that aren't in the CLUSTER, SERVICE * or SERVICE-SMOKETEST contexts. */ if( (txnProgressState.subTxnType != 'CLUSTER') && (txnProgressState.subTxnType != 'SERVICE') && (txnProgressState.subTxnType != 'SERVICE-SMOKETEST') ) { skipIt = true; } return skipIt; } var generateSingleTxnProgressStateMarkup = function( txnProgressStateTitle, txnProgressStateCssClass ) { globalYui.log( 'Generating: ' + txnProgressStateTitle + '-' + txnProgressStateCssClass ); var markup = '
  • ' + '
    ' + /* TODO XXX Format this nicer by camel-casing and putting the status in parentheses. */ txnProgressStateTitle + '
    ' + '
  • '; globalYui.log("XXX" + markup); return markup; } /**************************** Public data members ********************************/ globalYui.log( 'Got txnId:' + txnProgressContext.txnId ); this.txnProgressContext = txnProgressContext; this.txnProgressStatusMessage = txnProgressStatusMessage; this.txnProgressPostCompletionFixup = txnProgressPostCompletionFixup; var requestStr = '?clusterName=' + this.txnProgressContext.clusterName + '&txnId=' + this.txnProgressContext.txnId; if ("deployUser" in this.txnProgressContext) { requestStr += '&deployUser=' + this.txnProgressContext.deployUser; } var pdpDataSourceContext = { source: '../php/frontend/fetchTxnProgress.php', schema: { metaFields: { progress: 'progress' } }, request: requestStr, pollInterval: 3000, maxFailedAttempts: 5 }; var pdpResponseHandler = { success: function (e, pdp) { /* What we're here to render. */ var txnProgressMarkup = ''; var noNeedForFurtherPolling = false; var txnProgressStatusDivContent = ''; var txnProgressStatusDivCssClass = ''; var txnProgress = e.response.meta.progress; /* Guard against race conditions where txnProgress is null because the * txn hasn't had time to be kicked off yet. */ if (txnProgress) { /* The first time we get back meaningful progress data, pause the * automatic polling to avoid race conditions where response N+1 * is made (and returns with fresh data) while request N hasn't * yet been fully processed. * * We'll unpause at the end, after we've performed the rendering * of the updated states. */ pdp.pause(); var txnProgressStates = txnProgress.subTxns || []; globalYui.log(globalYui.Lang.dump(txnProgressStates)); txnProgressMarkup = ''; /* Make sure we have some progress data to show - if not, * we'll just show a loading image until this is non-null. * * The additional check for txnProgress.processRunning is to account * for cases where there are no subTxns (because it's all a no-op at * the backend) - the loading image should only be shown as long as * the backend is still working; after that, we should break out of * the loading image loop and let the user know that there was * nothing to be done. */ if( txnProgress.subTxns == null ) { if( txnProgress.processRunning == 0 ) { txnProgressMarkup = '
    ' + '
    ' + 'Nothing to do for this transaction; enjoy the freebie!' + '
    ' + '
    '; } else { txnProgressMarkup = ''; } } /* We can break this polling cycle in one of 2 ways: * * 1) If we are explicitly told by the backend that we're done. */ if( txnProgress.processRunning == 0 ) { noNeedForFurtherPolling = true; /* Be optimistic and assume that no errors were encountered (we'll * get more in touch with reality further below). */ txnProgressStatusDivContent = this.txnProgressStatusMessage.success; txnProgressStatusDivCssClass = 'statusOk'; } /* 2) If we encounter an error. * * Note how this is placed after the previous check, so as to serve * as an override in case the backend explicitly told us that we're * done, but an error was encountered in that very last progress report. */ if( txnProgress.encounteredError ) { noNeedForFurtherPolling = true; txnProgressStatusDivContent = this.txnProgressStatusMessage.failure; txnProgressStatusDivCssClass = 'statusError'; } } /* Render txnProgressMarkup before making any decisions about the * future state of pdp. */ globalYui.log('About to generate markup: ' + txnProgressMarkup); globalYui.one('#txnProgressDynamicRenderDivId').setContent( txnProgressMarkup ); /* And before checking out, decide whether we're done with this txn * or whether any more polling is required. */ if (noNeedForFurtherPolling) { /* We've made all the progress we could have, so stop polling. */ pdp.stop(); var txnProgressStatusDiv = globalYui.one('#txnProgressStatusDivId'); txnProgressStatusDiv.addClass(txnProgressStatusDivCssClass); txnProgressStatusDiv.one('#txnProgressStatusMessageDivId').setContent(txnProgressStatusDivContent); txnProgressStatusDiv.setStyle('display', 'block'); /* Run the post-completion fixups. */ if (txnProgressStatusDivCssClass == 'statusOk') { if (this.txnProgressPostCompletionFixup.success) { this.txnProgressPostCompletionFixup.success(this); } } else if (txnProgressStatusDivCssClass == 'statusError') { if (this.txnProgressPostCompletionFixup.failure) { this.txnProgressPostCompletionFixup.failure(this); } } } else { /* There's still more progress to be made, so unpause. */ pdp.unPause(); } }.bind(this), failure: function (e, pdp) { alert('Failed to fetch more progress!'); }.bind(this) }; this.periodicDataPoller = new PeriodicDataPoller( pdpDataSourceContext, pdpResponseHandler ); } TxnProgressWidget.prototype.show = function() { /* Start with a clean slate for #txnProgressStatusDivId, regardless of * the mess previous uses might have left it in. */ var txnProgressStatusDiv = globalYui.one('#txnProgressStatusDivId'); txnProgressStatusDiv.one('#txnProgressStatusMessageDivId').setContent(''); txnProgressStatusDiv.one('#txnProgressStatusActionsDivId').setContent(''); /* Remove the CSS statusOk/statusError classes from txnProgressStatusDiv * as well - sure would be nice to remove all classes that match a * pattern, but oh well. */ txnProgressStatusDiv.removeClass('statusOk'); txnProgressStatusDiv.removeClass('statusError'); /* Similarly, set a clean slate for #txnProgressDynamicRenderDivId as well. */ globalYui.one('#txnProgressDynamicRenderDivId').setContent ( '' ); globalYui.one('#blackScreenDivId').setStyle('display', 'block'); globalYui.one('#txnProgressCoreDivId').setStyle('display','block'); this.periodicDataPoller.start(); } TxnProgressWidget.prototype.hide = function() { this.periodicDataPoller.stop(); globalYui.one('#txnProgressStatusDivId').setStyle('display', 'none'); globalYui.one('#txnProgressCoreDivId').setStyle('display','none'); globalYui.one('#blackScreenDivId').setStyle('display', 'none'); }