SummaryFeature.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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 Cognos Products: Dashboard
  6. * (C) Copyright IBM Corp. 2018, 2020
  7. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  8. *
  9. */
  10. define(['underscore', './SummaryTypes', './SummaryQueryResult', '../../../widgets/livewidget/nls/StringResources', '../../../filters/FilterSpecHelper'], function (_, SummaryTypes, SummaryQueryResult, StringResources, FilterSpecHelper) {
  11. 'use strict';
  12. var OVERALL_SUMMARY_QUERY_KEY = '_overallSummaryQueryKey';
  13. /**
  14. * The current implementation requires a summary query for each summary set which may introduce performance issues.
  15. * Add limit to restrict number of queries until we can send the summary queries together
  16. */
  17. var NESTED_ITEM_LIMIT = 12;
  18. /**
  19. * @class SummaryFeature
  20. * Summary feature to run query for a visualization,
  21. * based on different combination of projections and query context.
  22. */
  23. var SummaryFeature = function () {
  24. function SummaryFeature(_ref) {
  25. var content = _ref.content,
  26. features = _ref.features;
  27. _classCallCheck(this, SummaryFeature);
  28. // Local object to store category and measure data item unique ids.
  29. // Used to build summary query projections and query UIs to get summary result.
  30. this._oQueryProjectionObj = {};
  31. // Object to store summary query result and is accessible by projected category unique ids.
  32. this._oQueriesResult = {};
  33. this.logger = features['Dashboard.Logger'];
  34. this.visualization = features['Visualization'];
  35. this.renderSequence = features['RenderSequence'];
  36. this.renderSequence.registerRenderStepProvider(this);
  37. this.content = content;
  38. this._api = {
  39. getSummary: this._getSummary.bind(this),
  40. isNotSupported: this.isNotSupported.bind(this)
  41. };
  42. }
  43. /**
  44. * @override
  45. * @public
  46. * @function getAPI
  47. * Get the Summary feature API
  48. * @returns the public api of the summary feature that is accessible by clients.
  49. */
  50. SummaryFeature.prototype.getAPI = function getAPI() {
  51. return this._api;
  52. };
  53. /**
  54. * Load the feature if a data slot has "summaries" in slot definition,
  55. * and "summaryValues" in the same (table) or another (e.g. crossTab) slot definition with data items
  56. * of aggregation type belongs to any of the following:
  57. * [
  58. 'none',
  59. 'countdistinct',
  60. 'avg',
  61. 'calculated',
  62. 'automatic'
  63. ]
  64. */
  65. SummaryFeature.prototype.isEnabled = function isEnabled() {
  66. return _.find(this.visualization.getSlots().getSlotList(), function (slot) {
  67. return slot.getDefinition().getProperty('summaries') || slot.getDefinition().getProperty('summaryValues');
  68. });
  69. };
  70. /**
  71. * Check unsupported conditions:
  72. * 1) If crossjoin of data items set is greater than limit
  73. * 2) no value filters in the widget (we can try to allow this in relational)
  74. * 3) no OLAP
  75. */
  76. SummaryFeature.prototype.isNotSupported = function isNotSupported() {
  77. // 1. Not supported when number of nesting is greater than limit.
  78. var numberOfNest = 1;
  79. var slotAPIs = this.visualization.getSlots().getMappedSlotList();
  80. _.each(slotAPIs, function (slotAPI) {
  81. numberOfNest = slotAPI.getDefinition().getProperty('summaries') ? numberOfNest * slotAPI.getDataItemList().length : numberOfNest;
  82. });
  83. var isNotSupported = numberOfNest > NESTED_ITEM_LIMIT;
  84. if (isNotSupported) {
  85. return isNotSupported;
  86. }
  87. // 2. Not supported when there is post fact filter.
  88. var localFilters = this.visualization.getLocalFilters().getFilterList();
  89. isNotSupported = _.find(localFilters, function (entry) {
  90. return entry.aggregationType && entry.preOrPost === 'post' && FilterSpecHelper.isRange(entry);
  91. }) !== undefined;
  92. if (isNotSupported) {
  93. return isNotSupported;
  94. }
  95. // 3. Not supported when is OLAP
  96. // Just check one dataItem since data items are from all the same data source
  97. var dataItemAPIs = slotAPIs && slotAPIs.length && slotAPIs[0].getDataItemList();
  98. isNotSupported = !dataItemAPIs || dataItemAPIs.length === 0 || !dataItemAPIs[0].getMetadataColumn() || dataItemAPIs[0].getMetadataColumn().isOlapColumn();
  99. if (isNotSupported) {
  100. return isNotSupported;
  101. }
  102. var topBottoms = {};
  103. slotAPIs.forEach(function (slot) {
  104. var dataItems = slot.getDataItemList();
  105. dataItems.forEach(function (dataItem) {
  106. if (dataItem.getTopBottom()) {
  107. // 4. Not supported when there is a topBottom defined on a fact data item and is in summaryValues slot (RTC#250663)
  108. if (slot.getDefinition().getProperty('summaryValues') && dataItem.getType() === 'fact') {
  109. isNotSupported = true;
  110. }
  111. // Map of topBottoms per slot
  112. if (!topBottoms[slot.getId()]) {
  113. topBottoms[slot.getId()] = [];
  114. }
  115. topBottoms[slot.getId()].push(dataItem);
  116. }
  117. });
  118. });
  119. if (isNotSupported) {
  120. return isNotSupported;
  121. }
  122. // 5. Not supported when there are top/bottom on different slots (RTC#243655)
  123. return _.size(topBottoms) > 1;
  124. };
  125. /**
  126. * @override
  127. * @public
  128. * @function getExtraRenderSequenceSteps
  129. * Get the extra render sequence task required for the summary feature
  130. * @returns {Object[]} Array of task definitions
  131. */
  132. SummaryFeature.prototype.getRenderStepList = function getRenderStepList() {
  133. return this.isEnabled() ? [{
  134. id: 'data-summary',
  135. dependencies: ['data'],
  136. modulePath: 'dashboard-analytics/extensions/client-summaries/content-features/SummaryTask',
  137. moduleOptions: {
  138. summaryFeature: this,
  139. visualization: this.visualization,
  140. content: this.content
  141. }
  142. }] : [];
  143. };
  144. /**
  145. * @private
  146. * @public
  147. * @function _getSummary
  148. * Get summary result by UIs
  149. * @param {string[]} categoryUIds - array of unique category data item ids to identify a summary query result
  150. * @param {string} measureUId - the unique id of the measure to get the summary
  151. * @param {string[]} tupleIds - array of unique tuple id to get a summary tuple cell
  152. * @returns {Promise} A promise which resolves the summary query result when the result is ready
  153. */
  154. SummaryFeature.prototype._getSummary = function _getSummary(categoryUIds, measureUId, tupleIds) {
  155. var _this = this;
  156. var originalCategoryUIds = categoryUIds.slice(0);
  157. var queryResultPromise = this._oQueriesResult[this._getCategoryQueryKey(categoryUIds)];
  158. if (queryResultPromise) {
  159. return queryResultPromise.then(function (summaryResult) {
  160. return summaryResult.getValue(originalCategoryUIds, measureUId, tupleIds);
  161. }).catch(function (error) {
  162. // return Promise.reject(this._buildErrorObj(error));
  163. return _this._buildErrorObj(error);
  164. });
  165. } else {
  166. return Promise.resolve(this._buildErrorObj());
  167. }
  168. };
  169. /**
  170. * @returns error object from different error case
  171. */
  172. SummaryFeature.prototype._buildErrorObj = function _buildErrorObj() {
  173. return {
  174. error: StringResources.get('errorCellWarning')
  175. };
  176. };
  177. /**
  178. * @returns {object} an object of an array of combination of category unique ids of the widget and an array of the measures with aggregation
  179. * types in SUMMARY_TYPES. If a widget has slot1 = [A, B, C], slot2 = [D, E, F], value slot = [G, H] where aggregate for G and H is avg.
  180. * @example result = {
  181. * aCategoryUIds: [[A],[AB],[ABC],[D],[DE],[EDF],[AD],[ADE],[ADEF],[ABD],[ABDE],[ABDEF],[ABCD],[ABCDE]],
  182. * aOrdinalUIds: [G, H]
  183. * }
  184. */
  185. SummaryFeature.prototype.getProjectionListObj = function getProjectionListObj() {
  186. this._oQueryProjectionObj = {};
  187. var aSummarySlotsDataItems = []; // An array of category slot dataItems array where the summary is corresponding to
  188. var aSummaryValuesDataItems = []; // An array of ordinal slot dataItems array where the summary is applied t
  189. var dataSlots = this.visualization.getSlots().getMappedSlotList();
  190. dataSlots.forEach(function (slotAPI) {
  191. if (slotAPI.getDefinition().getProperty('summaries')) {
  192. var categoryDataItems = _.filter(slotAPI.getDataItemList(), function (dataItem) {
  193. return dataItem.getType() === 'attribute';
  194. });
  195. if (categoryDataItems) {
  196. aSummarySlotsDataItems.push(categoryDataItems);
  197. }
  198. }
  199. if (slotAPI.getDefinition().getProperty('summaryValues')) {
  200. var ordinalDataItems = _.filter(slotAPI.getDataItemList(), function (dataItem) {
  201. return SummaryTypes.indexOf(dataItem.getAggregation()) !== -1 && dataItem.getType() === 'fact';
  202. });
  203. aSummaryValuesDataItems = aSummaryValuesDataItems.concat(ordinalDataItems);
  204. }
  205. });
  206. if (aSummarySlotsDataItems.length || aSummaryValuesDataItems.length) {
  207. if (aSummarySlotsDataItems.length) {
  208. // 1) Build unique id sets from all data items of all category slots. E.g. slot1 = [A, B, C], slot2 = [D, E, F]
  209. // aSlotDataItemUIdSets = [
  210. // [A, AB, ABC],
  211. // [D, DE, EDF]
  212. // ]
  213. var aSlotDataItemUIdSets = this._buildIdSetsFromSlotsDataItems(aSummarySlotsDataItems);
  214. // 2) Build cross join set from direct unique id sets
  215. if (aSlotDataItemUIdSets.length) {
  216. aSlotDataItemUIdSets = this._crossJoinSlotUIdSets(aSlotDataItemUIdSets);
  217. }
  218. var aResultCategoryUIds = _.flatten(aSlotDataItemUIdSets, true);
  219. aResultCategoryUIds.pop(); // Remove the detail unique id combinations
  220. this._oQueryProjectionObj.aCategoryUIds = aResultCategoryUIds;
  221. } else {
  222. this._oQueryProjectionObj.aCategoryUIds = [];
  223. }
  224. if (aSummaryValuesDataItems.length) {
  225. // Array of summary values slots dataItems UIs.
  226. var aSummaryValuesUIds = _.map(aSummaryValuesDataItems, function (valueDataItem) {
  227. return valueDataItem.getId();
  228. });
  229. this._oQueryProjectionObj.aOrdinalUIds = aSummaryValuesUIds;
  230. } else {
  231. this._oQueryProjectionObj.aOrdinalUIds = [];
  232. }
  233. }
  234. return this._oQueryProjectionObj;
  235. };
  236. SummaryFeature.prototype._buildIdSetsFromSlotsDataItems = function _buildIdSetsFromSlotsDataItems(aSummarySlotsDataItems) {
  237. var aSlotDataItemUIdSets = [];
  238. aSummarySlotsDataItems.forEach(function (dataItems) {
  239. var currentUIds = [];
  240. var aUIdsPerSlot = [];
  241. dataItems.forEach(function (dataItem) {
  242. currentUIds = currentUIds.concat(dataItem.getId());
  243. aUIdsPerSlot.push(currentUIds);
  244. });
  245. aSlotDataItemUIdSets.push(aUIdsPerSlot);
  246. });
  247. return aSlotDataItemUIdSets;
  248. };
  249. SummaryFeature.prototype._crossJoinSlotUIdSets = function _crossJoinSlotUIdSets(aSlotDataItemUIdSets) {
  250. var crossjointUIds = [];
  251. for (var i = 0; i < aSlotDataItemUIdSets.length; i++) {
  252. var aSet1 = aSlotDataItemUIdSets[i];
  253. var j = i + 1;
  254. while (aSlotDataItemUIdSets[j] && aSlotDataItemUIdSets[j].length) {
  255. crossjointUIds.push(this._crossJoin(aSet1, aSlotDataItemUIdSets[j]));
  256. j++;
  257. }
  258. }
  259. return aSlotDataItemUIdSets.concat(crossjointUIds);
  260. };
  261. /**
  262. * Build a crossjoin set of 2 sets.
  263. * @example
  264. * set1 = [A, AB, ABC]
  265. * set2 = [D, DE, DEF]
  266. * result = [
  267. * AD,
  268. * ADE,
  269. * ADEF,
  270. * ABD,
  271. * ABDE,
  272. * ABDEF,
  273. * ABCD,
  274. * ABCDE,
  275. * ABCDEF // The last element generated is a detail row
  276. * ]
  277. *
  278. * @param {Array} aUIdSet1 first set which is an array of unique UIs
  279. * @param {Array} aUIdSet2 second set. An array of unique UIs
  280. * @returns a crossjoin set of aUIdSet1 and aUIdSet2
  281. */
  282. SummaryFeature.prototype._crossJoin = function _crossJoin(aUIdSet1, aUIdSet2) {
  283. var crossjointUIds = [];
  284. aUIdSet1.forEach(function (keySet1) {
  285. var jointKeys = [];
  286. aUIdSet2.forEach(function (keySet2) {
  287. jointKeys = keySet1.concat(keySet2);
  288. crossjointUIds.push(jointKeys);
  289. });
  290. });
  291. return crossjointUIds;
  292. };
  293. /**
  294. * @private
  295. * @public
  296. * Add a summary result to queries map.
  297. * The corresponding entry is the query promise.
  298. * @param {integer} idx the index of the aCategoryUIds
  299. * @param {Object} queryPromise the summary query promise
  300. */
  301. SummaryFeature.prototype.addSummaryQueryResult = function addSummaryQueryResult(idx, queryPromise) {
  302. var _this2 = this;
  303. this._oQueriesResult[this._getCategoryQueryKey(this._oQueryProjectionObj.aCategoryUIds[idx])] = queryPromise.then(function (queryResult) {
  304. return new SummaryQueryResult(queryResult.getResult(), _this2.logger);
  305. }).catch(function (error) {
  306. _this2.logger.error(error);
  307. return Promise.reject(error);
  308. });
  309. };
  310. /**
  311. * @private
  312. * @param {string []} aCategoryUId - an array of strings represent the category unique ids.
  313. * @returns string - unique summary query key
  314. */
  315. SummaryFeature.prototype._getCategoryQueryKey = function _getCategoryQueryKey(aCategoryUId) {
  316. // The key is built from all category data item unique ids.
  317. // The overall summary query doesn't have any category unique id.
  318. var uniqueIds = aCategoryUId && aCategoryUId.length ? aCategoryUId : [OVERALL_SUMMARY_QUERY_KEY];
  319. uniqueIds.sort();
  320. return uniqueIds.join();
  321. };
  322. return SummaryFeature;
  323. }();
  324. return SummaryFeature;
  325. });
  326. //# sourceMappingURL=SummaryFeature.js.map