txnUtils.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. function TxnProgressWidget( txnProgressContext, txnProgressStatusMessage, txnProgressPostCompletionFixup ) {
  2. /**************************** Private methods ********************************/
  3. var txnProgressStateShouldBeSkipped = function( txnProgressState ) {
  4. var skipIt = false;
  5. /* Step over any deploy progress states that aren't in the CLUSTER, SERVICE
  6. * or SERVICE-SMOKETEST contexts.
  7. */
  8. if( (txnProgressState.subTxnType != 'CLUSTER') &&
  9. (txnProgressState.subTxnType != 'SERVICE') &&
  10. (txnProgressState.subTxnType != 'SERVICE-SMOKETEST') ) {
  11. skipIt = true;
  12. }
  13. return skipIt;
  14. }
  15. var generateSingleTxnProgressStateMarkup = function( txnProgressStateTitle, txnProgressStateCssClass ) {
  16. globalYui.log( 'Generating: ' + txnProgressStateTitle + '-' + txnProgressStateCssClass );
  17. var markup =
  18. '<li>' +
  19. '<div class=' + txnProgressStateCssClass + '>' +
  20. /* TODO XXX Format this nicer by camel-casing and putting the status in parentheses. */
  21. txnProgressStateTitle +
  22. '</div>' +
  23. '</li>';
  24. globalYui.log("XXX" + markup);
  25. return markup;
  26. }
  27. /**************************** Public data members ********************************/
  28. globalYui.log( 'Got txnId:' + txnProgressContext.txnId );
  29. this.txnProgressContext = txnProgressContext;
  30. this.txnProgressStatusMessage = txnProgressStatusMessage;
  31. this.txnProgressPostCompletionFixup = txnProgressPostCompletionFixup;
  32. var requestStr = '?clusterName=' + this.txnProgressContext.clusterName + '&txnId=' + this.txnProgressContext.txnId;
  33. if ("deployUser" in this.txnProgressContext) {
  34. requestStr += '&deployUser=' + this.txnProgressContext.deployUser;
  35. }
  36. var pdpDataSourceContext = {
  37. source: '../php/frontend/fetchTxnProgress.php',
  38. schema: {
  39. metaFields: {
  40. progress: 'progress'
  41. }
  42. },
  43. request: requestStr,
  44. pollInterval: 3000,
  45. maxFailedAttempts: 5
  46. };
  47. var pdpResponseHandler = {
  48. success: function (e, pdp) {
  49. /* What we're here to render. */
  50. var txnProgressMarkup =
  51. '<img id=txnProgressLoadingImgId class=loadingImg src=../images/loading.gif />';
  52. var noNeedForFurtherPolling = false;
  53. var txnProgressStatusDivContent = '';
  54. var txnProgressStatusDivCssClass = '';
  55. var txnProgress = e.response.meta.progress;
  56. /* Guard against race conditions where txnProgress is null because the
  57. * txn hasn't had time to be kicked off yet.
  58. */
  59. if (txnProgress) {
  60. /* The first time we get back meaningful progress data, pause the
  61. * automatic polling to avoid race conditions where response N+1
  62. * is made (and returns with fresh data) while request N hasn't
  63. * yet been fully processed.
  64. *
  65. * We'll unpause at the end, after we've performed the rendering
  66. * of the updated states.
  67. */
  68. pdp.pause();
  69. var txnProgressStates = txnProgress.subTxns || [];
  70. globalYui.log(globalYui.Lang.dump(txnProgressStates));
  71. txnProgressMarkup = '<ul id=txnProgressStatesListId>';
  72. var progressStateIndex = 0;
  73. /* Generate markup for all the "done" states. */
  74. for( ; progressStateIndex < txnProgressStates.length; ++progressStateIndex ) {
  75. var presentTxnProgressState = txnProgressStates[ progressStateIndex ];
  76. /* Step over any progress states that don't deserve to be shown. */
  77. if( txnProgressStateShouldBeSkipped( presentTxnProgressState ) ) {
  78. continue;
  79. }
  80. /* The first sign of a state that isn't done, and we're outta here. */
  81. if( presentTxnProgressState.progress != 'COMPLETED' ) {
  82. break;
  83. }
  84. globalYui.log( 'Done loop - ' + progressStateIndex );
  85. txnProgressMarkup += generateSingleTxnProgressStateMarkup
  86. ( presentTxnProgressState.description, 'txnProgressStateDone' );
  87. globalYui.log("Currently, markup is:" + txnProgressMarkup );
  88. }
  89. /* Next, generate markup for the first "in-progress" state. */
  90. for( ; progressStateIndex < txnProgressStates.length; ++progressStateIndex ) {
  91. var presentTxnProgressState = txnProgressStates[ progressStateIndex ];
  92. /* Step over any progress states that don't deserve to be shown. */
  93. if( txnProgressStateShouldBeSkipped( presentTxnProgressState ) ) {
  94. continue;
  95. }
  96. /* The first state that shouldn't be skipped is marked as being
  97. * "in-progress", even if presentTxnProgressState.progress is
  98. * not explicitly set to "IN_PROGRESS".
  99. *
  100. * This is to take care of race conditions where the poll to the
  101. * backend is made at a time when the previous state has
  102. * "COMPLETED" but the next state hasn't been started yet (which
  103. * means it's "PENDING") - if we were explicitly looking for
  104. * "IN_PROGRESS", there'd be nothing to show in this loop and it
  105. * would run to the end of txnProgressStates hunting for that
  106. * elusive "IN_PROGRESS", thus not even showing any of the
  107. * "PENDING" states, causing a momentary jitter in the rendering
  108. * (see AMBARI-344 for an example).
  109. */
  110. globalYui.log( 'In-progress/failed - ' + progressStateIndex );
  111. /* Decide upon what CSS class to assign to the currently-in-progress
  112. * state - if an error was marked as having been encountered, assign
  113. * the fitting .txnProgressStateError, else just annoint it with
  114. * .txnProgressStateInProgress
  115. */
  116. var currentProgressStateCssClass = 'txnProgressStateInProgress';
  117. /* The 2 possible indications of error are:
  118. *
  119. * a) presentTxnProgressState.progress is 'IN_PROGRESS' but
  120. * txnProgress.encounteredError is true.
  121. * b) presentTxnProgressState.progress is 'FAILED'.
  122. */
  123. if( (txnProgress.encounteredError) ||
  124. (presentTxnProgressState.progress == 'FAILED') ) {
  125. currentProgressStateCssClass = 'txnProgressStateError';
  126. }
  127. /* And generate markup for this "in-progress" state. */
  128. txnProgressMarkup += generateSingleTxnProgressStateMarkup
  129. ( presentTxnProgressState.description, currentProgressStateCssClass );
  130. /* It's important to manually increment progressStateIndex here,
  131. * to set it up correctly for the upcoming loop.
  132. */
  133. ++progressStateIndex;
  134. /* Remember, we only care for the FIRST "in-progress" state.
  135. *
  136. * Any following "in-progress" states will all be marked as
  137. * "pending", so as to avoid the display from becoming
  138. * disorienting (with multiple states "in-progress").
  139. */
  140. break;
  141. }
  142. /* Finally, generate markup for all the "pending" states. */
  143. for( ; progressStateIndex < txnProgressStates.length; ++progressStateIndex ) {
  144. var presentTxnProgressState = txnProgressStates[ progressStateIndex ];
  145. /* Step over any progress states that don't deserve to be shown. */
  146. if( txnProgressStateShouldBeSkipped( presentTxnProgressState ) ) {
  147. continue;
  148. }
  149. globalYui.log( 'Pending loop - ' + progressStateIndex );
  150. txnProgressMarkup += generateSingleTxnProgressStateMarkup
  151. ( presentTxnProgressState.description, 'txnProgressStatePending' );
  152. }
  153. txnProgressMarkup += '</ul>';
  154. /* Make sure we have some progress data to show - if not,
  155. * we'll just show a loading image until this is non-null.
  156. *
  157. * The additional check for txnProgress.processRunning is to account
  158. * for cases where there are no subTxns (because it's all a no-op at
  159. * the backend) - the loading image should only be shown as long as
  160. * the backend is still working; after that, we should break out of
  161. * the loading image loop and let the user know that there was
  162. * nothing to be done.
  163. */
  164. if( txnProgress.subTxns == null ) {
  165. if( txnProgress.processRunning == 0 ) {
  166. txnProgressMarkup =
  167. '<br/>' +
  168. '<div class=txnNoOpMsg>' +
  169. 'Nothing to do for this transaction; enjoy the freebie!' +
  170. '</div>' +
  171. '<br/>';
  172. }
  173. else {
  174. txnProgressMarkup =
  175. '<img id=txnProgressLoadingImgId class=loadingImg src=../images/loading.gif />';
  176. }
  177. }
  178. /* We can break this polling cycle in one of 2 ways:
  179. *
  180. * 1) If we are explicitly told by the backend that we're done.
  181. */
  182. if( txnProgress.processRunning == 0 ) {
  183. noNeedForFurtherPolling = true;
  184. /* Be optimistic and assume that no errors were encountered (we'll
  185. * get more in touch with reality further below).
  186. */
  187. txnProgressStatusDivContent = this.txnProgressStatusMessage.success;
  188. txnProgressStatusDivCssClass = 'statusOk';
  189. }
  190. /* 2) If we encounter an error.
  191. *
  192. * Note how this is placed after the previous check, so as to serve
  193. * as an override in case the backend explicitly told us that we're
  194. * done, but an error was encountered in that very last progress report.
  195. */
  196. if( txnProgress.encounteredError ) {
  197. noNeedForFurtherPolling = true;
  198. txnProgressStatusDivContent = this.txnProgressStatusMessage.failure;
  199. txnProgressStatusDivCssClass = 'statusError';
  200. }
  201. }
  202. /* Render txnProgressMarkup before making any decisions about the
  203. * future state of pdp.
  204. */
  205. globalYui.log('About to generate markup: ' + txnProgressMarkup);
  206. globalYui.one('#txnProgressDynamicRenderDivId').setContent( txnProgressMarkup );
  207. /* And before checking out, decide whether we're done with this txn
  208. * or whether any more polling is required.
  209. */
  210. if (noNeedForFurtherPolling) {
  211. /* We've made all the progress we could have, so stop polling. */
  212. pdp.stop();
  213. var txnProgressStatusDiv = globalYui.one('#txnProgressStatusDivId');
  214. txnProgressStatusDiv.addClass(txnProgressStatusDivCssClass);
  215. txnProgressStatusDiv.one('#txnProgressStatusMessageDivId').setContent(txnProgressStatusDivContent);
  216. txnProgressStatusDiv.setStyle('display', 'block');
  217. /* Run the post-completion fixups. */
  218. if (txnProgressStatusDivCssClass == 'statusOk') {
  219. if (this.txnProgressPostCompletionFixup.success) {
  220. this.txnProgressPostCompletionFixup.success(this);
  221. }
  222. }
  223. else if (txnProgressStatusDivCssClass == 'statusError') {
  224. if (this.txnProgressPostCompletionFixup.failure) {
  225. this.txnProgressPostCompletionFixup.failure(this);
  226. }
  227. }
  228. }
  229. else {
  230. /* There's still more progress to be made, so unpause. */
  231. pdp.unPause();
  232. }
  233. }.bind(this),
  234. failure: function (e, pdp) {
  235. alert('Failed to fetch more progress!');
  236. }.bind(this)
  237. };
  238. this.periodicDataPoller = new PeriodicDataPoller( pdpDataSourceContext, pdpResponseHandler );
  239. }
  240. TxnProgressWidget.prototype.show = function() {
  241. /* Start with a clean slate for #txnProgressStatusDivId, regardless of
  242. * the mess previous uses might have left it in.
  243. */
  244. var txnProgressStatusDiv = globalYui.one('#txnProgressStatusDivId');
  245. txnProgressStatusDiv.one('#txnProgressStatusMessageDivId').setContent('');
  246. txnProgressStatusDiv.one('#txnProgressStatusActionsDivId').setContent('');
  247. /* Remove the CSS statusOk/statusError classes from txnProgressStatusDiv
  248. * as well - sure would be nice to remove all classes that match a
  249. * pattern, but oh well.
  250. */
  251. txnProgressStatusDiv.removeClass('statusOk');
  252. txnProgressStatusDiv.removeClass('statusError');
  253. /* Similarly, set a clean slate for #txnProgressDynamicRenderDivId as well. */
  254. globalYui.one('#txnProgressDynamicRenderDivId').setContent
  255. ( '<img id=txnProgressLoadingImgId class=loadingImg src=../images/loading.gif />' );
  256. globalYui.one('#blackScreenDivId').setStyle('display', 'block');
  257. globalYui.one('#txnProgressCoreDivId').setStyle('display','block');
  258. this.periodicDataPoller.start();
  259. }
  260. TxnProgressWidget.prototype.hide = function() {
  261. this.periodicDataPoller.stop();
  262. globalYui.one('#txnProgressStatusDivId').setStyle('display', 'none');
  263. globalYui.one('#txnProgressCoreDivId').setStyle('display','none');
  264. globalYui.one('#blackScreenDivId').setStyle('display', 'none');
  265. }