Transaction.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. 'use strict';
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Business Analytics (C) Copyright IBM Corp. 2019, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. /**
  9. * @class TransactionAPI
  10. * @hideconstructor
  11. * @classdesc API class that is used to control transactional operations across API calls
  12. */
  13. define(['underscore', '../../../../../lib/@waca/dashboard-common/dist/core/APIFactory', '../TransactionAPI'], function (_, APIFactory, TransactionAPI) {
  14. var Transaction = function () {
  15. function Transaction() {
  16. _classCallCheck(this, Transaction);
  17. this._transactions = new Map();
  18. this._handlers = new Map();
  19. }
  20. Transaction.prototype.getAPI = function getAPI() {
  21. return APIFactory.createAPI(this, [TransactionAPI]);
  22. };
  23. Transaction.prototype.startTransaction = function startTransaction(parentToken) {
  24. if (!(parentToken && parentToken.transactionId)) {
  25. // create a new transaction entry
  26. var entry = this._createTransactionEntry(_.uniqueId('__transaction__'));
  27. if (parentToken && parentToken.source) {
  28. entry.token.source = parentToken.source;
  29. entry.token.eventGenerated = parentToken.eventGenerated;
  30. }
  31. return entry.token;
  32. } else {
  33. var _entry = this._get(parentToken.transactionId);
  34. if (_entry) {
  35. // add a sub-transaction to an existing transaction entry
  36. var subTransactionIdx = this._addSubTransaction(_entry);
  37. var res = _.extend({ subTransaction: subTransactionIdx }, _entry.token);
  38. if (parentToken && parentToken.source) {
  39. res.source = parentToken.source;
  40. res.eventGenerated = parentToken.eventGenerated;
  41. }
  42. return res;
  43. }
  44. console.warn('Invalid transactionId: ' + parentToken.transactionId);
  45. return parentToken;
  46. }
  47. };
  48. Transaction.prototype.startTransactionById = function startTransactionById(transactionId) {
  49. var entry = this._get(transactionId);
  50. if (entry) {
  51. return this.startTransaction(entry.token);
  52. }
  53. entry = this._createTransactionEntry(transactionId);
  54. return entry.token;
  55. };
  56. Transaction.prototype.endTransaction = function endTransaction(token) {
  57. var transactionId = token && token.transactionId;
  58. var entry = this._get(transactionId);
  59. if (entry && Number.isInteger(token.subTransaction)) {
  60. entry = entry.children[token.subTransaction];
  61. }
  62. if (entry) {
  63. entry.timestamp.end = Date.now();
  64. entry.end();
  65. } else {
  66. console.warn('Invalid transactionId: ' + transactionId);
  67. }
  68. return token;
  69. };
  70. Transaction.prototype.getTransactionById = function getTransactionById(id) {
  71. var entry = this._get(id);
  72. return entry ? entry.token : {};
  73. };
  74. Transaction.prototype.registerTransactionHandler = function registerTransactionHandler(token, handlerId, handler, args) {
  75. var isValid = this.isValidTransaction(token);
  76. if (isValid) {
  77. // register and invoke the handler
  78. this._register(handlerId, handler, token.transactionId, args);
  79. return this._invoke(handlerId);
  80. } else {
  81. // directly invoke the handler and move on
  82. var rv = handler([args]);
  83. return rv instanceof Promise ? rv : Promise.resolve();
  84. }
  85. };
  86. Transaction.prototype.whenTransactionComplete = function whenTransactionComplete(token) {
  87. // we only need wait for the root promise to complete
  88. var entry = this._get(token.transactionId);
  89. // return the transaction promise which disposes the transaction at the end
  90. return entry._promise.then(this._dispose.bind(this, token.transactionId));
  91. };
  92. Transaction.prototype.isValidTransaction = function isValidTransaction(token) {
  93. return !!this._get(token.transactionId);
  94. };
  95. Transaction.prototype.getActiveTransactionList = function getActiveTransactionList() {
  96. return _.map(this._getAll(), function (entry) {
  97. return entry.token;
  98. });
  99. };
  100. /**
  101. * @private
  102. * Create a new transaction entry
  103. * @param {String} uniqueId unique transaction identifier
  104. * {
  105. * token: {
  106. * transactionId: 'xyz'
  107. * },
  108. * end: function() { ... },
  109. * timestamp: {
  110. * start: 1551142727899,
  111. * end: 1551142741757
  112. * }
  113. * }
  114. */
  115. Transaction.prototype._createTransactionEntry = function _createTransactionEntry(uniqueId) {
  116. var entry = {
  117. 'token': {
  118. 'transactionId': uniqueId
  119. },
  120. 'timestamp': {
  121. 'start': Date.now()
  122. }
  123. };
  124. entry._promise = new Promise(function (resolve) {
  125. this.end = function () {
  126. if (this.children && this.children.length > 0) {
  127. this.children[0]._promise.then(resolve);
  128. } else {
  129. resolve();
  130. }
  131. }.bind(this);
  132. }.bind(entry));
  133. this._set(uniqueId, entry);
  134. return entry;
  135. };
  136. /**
  137. * @private
  138. * Add a sub-transcation to an existing transaction
  139. * @param {Object} entry parent transaction entry object
  140. * {
  141. * token: {
  142. * transactionId: 'xyz'
  143. * },
  144. * children: [{
  145. * end: function() { ... },
  146. * next: { ... }
  147. * timestamp: {
  148. * start: 1551142727899,
  149. * end: 1551142741757
  150. * }
  151. * }, {
  152. * end: function() { ... },
  153. * timestamp: {
  154. * start: 1551142727899,
  155. * end: 1551142741757
  156. * }
  157. * }]
  158. * end: function() { ... },
  159. * timestamp: {
  160. * start: 1551142727899
  161. * }
  162. * }
  163. */
  164. Transaction.prototype._addSubTransaction = function _addSubTransaction(entry) {
  165. // prepare the subtransactions list
  166. if (!entry.children) {
  167. entry.children = [];
  168. }
  169. var subTransactionEntry = {
  170. timestamp: {
  171. start: Date.now()
  172. }
  173. };
  174. var transactionIndex = entry.children.length;
  175. var lastItem = entry.children && entry.children[entry.children.length - 1];
  176. // link the current transaction as the next transaction for the previous transaction
  177. if (lastItem) {
  178. lastItem.next = subTransactionEntry;
  179. }
  180. subTransactionEntry._promise = new Promise(function (resolve) {
  181. this.end = function () {
  182. if (this.next) {
  183. // resolve the my promise once the next promise is resolved
  184. this.next._promise.then(resolve);
  185. } else {
  186. resolve();
  187. }
  188. }.bind(this);
  189. }.bind(subTransactionEntry));
  190. // add the subtransaction to the existing entry
  191. entry.children.push(subTransactionEntry);
  192. return transactionIndex;
  193. };
  194. Transaction.prototype._getAll = function _getAll() {
  195. var all = [];
  196. this._transactions.forEach(function (tranaction) {
  197. all.push(tranaction);
  198. });
  199. return all;
  200. };
  201. Transaction.prototype._get = function _get(transactionId) {
  202. return this._transactions.get(transactionId);
  203. };
  204. Transaction.prototype._set = function _set(transactionId, entry) {
  205. this._transactions.set(transactionId, entry);
  206. };
  207. Transaction.prototype._dispose = function _dispose(transactionId) {
  208. this._transactions.delete(transactionId);
  209. };
  210. Transaction.prototype._register = function _register(handlerId, handler, transactionId, args) {
  211. var entry = this._handlers.get(handlerId);
  212. if (entry) {
  213. // update the transactions related to current handler
  214. if (entry.transactionIds.indexOf(transactionId) < 0) {
  215. entry.transactionIds.push(transactionId);
  216. }
  217. entry.args.push(args);
  218. } else {
  219. // register the handler
  220. this._handlers.set(handlerId, {
  221. 'transactionIds': [transactionId],
  222. 'handler': handler,
  223. 'args': [args]
  224. });
  225. }
  226. };
  227. Transaction.prototype._isRegistered = function _isRegistered(handlerId) {
  228. return !!this._handlers.get(handlerId);
  229. };
  230. /**
  231. * This is an enhancement to replace Promise.all to handle cases
  232. * which the array of transactions can grow asynchronously.
  233. * Once the initial handler waits for all transactions complete,
  234. * transactions that are added afterwards may result the handler
  235. * to be invoked prematurely.
  236. */
  237. Transaction.prototype._waitForAllTransactions = function _waitForAllTransactions(transactionIds) {
  238. var _this = this;
  239. var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  240. if (transactionIds.length > index) {
  241. // We can not rely on Promise.all since a new transaction can be added
  242. // after the handler was initially register and invoked
  243. var transaction = this._get(transactionIds[index]);
  244. if (transaction) {
  245. // wait for the current transaction to complete
  246. return transaction._promise.then(function () {
  247. // seek for the next transaction during the runtime
  248. return _this._waitForAllTransactions(transactionIds, index + 1);
  249. });
  250. } else {
  251. // continue with the next transaction
  252. return this._waitForAllTransactions(transactionIds, index + 1);
  253. }
  254. }
  255. return Promise.resolve();
  256. };
  257. Transaction.prototype._invoke = function _invoke(handlerId) {
  258. var _this2 = this;
  259. var entry = this._handlers.get(handlerId);
  260. if (!entry._promise) {
  261. // @todo Use Transaction._waitForAllTransactions to support optimized rendering
  262. var transactions = _.map(entry.transactionIds, function (id) {
  263. return _this2._get(id)._promise;
  264. });
  265. // invoke the handler once all related transactions are complete
  266. entry._promise = Promise.all(transactions).then(function () {
  267. // entry._promise = this._waitForAllTransactions(entry.transactionIds).then(() => {
  268. // dispose all transactions that are registered for this handler
  269. _.each(entry.transactionIds, _this2._dispose.bind(_this2));
  270. try {
  271. return entry.handler(entry.args);
  272. } finally {
  273. // clean up the handler
  274. _this2._handlers.delete(handlerId);
  275. }
  276. });
  277. }
  278. return entry._promise;
  279. };
  280. return Transaction;
  281. }();
  282. return Transaction;
  283. });
  284. //# sourceMappingURL=Transaction.js.map