GridDataProcessor.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. 'use strict';
  2. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
  3. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  4. /**
  5. * Licensed Materials - Property of IBM
  6. * IBM Cognos Products: Dashboard
  7. * (C) Copyright IBM Corp. 2016, 2020
  8. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  9. */
  10. define(['../../../widgets/livewidget/nls/StringResources', '../../../util/DashboardFormatter', '../../../util/ArrayUtils', './GridEdgeIterator', './Summary', '../../../extensions/client-summaries/content-features/SummaryTypes', '../../../widgets/livewidget/query/QueryResultDataUtils'], function (StringResources, Formatter, ArrayUtils, GridEdgeIterator, Summary, SummaryTypes, QueryResultDataUtils) {
  11. 'use strict';
  12. var SUMMARY_ID = StringResources.get('summaryCaption');
  13. var NOT_AVAILABLE = StringResources.get('value_is_not_available');
  14. var UNDEFINED = 'undefined';
  15. var NONE = 'none';
  16. var OBJECT = 'object';
  17. var OVERALL = 'overall';
  18. var getUseValue = function getUseValue(oValue) {
  19. return oValue && _typeof(oValue.value) !== UNDEFINED ? oValue.value : oValue;
  20. };
  21. var getKeys = function getKeys(aData, bData) {
  22. var aKeys = [];
  23. if (Array.isArray(aData)) {
  24. for (var i = 0; i < aData.length; i++) {
  25. aKeys.push(getUseValue(aData[i]));
  26. }
  27. }
  28. if ((typeof bData === 'undefined' ? 'undefined' : _typeof(bData)) !== UNDEFINED) {
  29. aKeys.push(getUseValue(bData));
  30. }
  31. return aKeys.toString();
  32. };
  33. /**
  34. * !!! PERFORMANCE WARNING !!!
  35. * This class manipulates potentially very large amounts of data on the client. Any line of code may be
  36. * executed millions of times to render a single visualization, so should be as optimized as possible.
  37. */
  38. var GridDataProcessor = function () {
  39. function GridDataProcessor(options) {
  40. var _this = this;
  41. _classCallCheck(this, GridDataProcessor);
  42. this._queryResult = options.queryResult;
  43. this._numberOfColumns = options.numberOfColumns || 0;
  44. this._lastGroupedColumnIndex = options.lastGroupedColumnIndex || 0;
  45. this._ungroupedColumnsInfo = [];
  46. this._rankDataItems = options.rankDataItems || [];
  47. this._columnSlot = options.columnSlot;
  48. this._columnDataItems = options.columnDataItems || [];
  49. this._getRankFormat = options.getRankFormat;
  50. this._getRealColumnIndex = options.getRealColumnIndex;
  51. this._valuesMap = {};
  52. this._exceedsColumnThreshold = options.exceedsColumnThreshold || false;
  53. this._numberOfAttributeColumns = this._numberOfColumns - (options.factColumnIndices ? options.factColumnIndices.length : 0);
  54. this._doSummary = options.doSummary;
  55. this.summaryAPI = options.summaryFeature;
  56. this.summaryNotSupported = this.summaryAPI && this.summaryAPI.isNotSupported();
  57. this._onCellDataChange = options.onCellDataChange;
  58. this._summaryValuePromises = [];
  59. this._factColumnsMap = {};
  60. Array.isArray(options.factColumnIndices) && options.factColumnIndices.forEach(function (index) {
  61. return _this._factColumnsMap[index] = true;
  62. });
  63. }
  64. /**
  65. * onCellDataChange callback wrapper for formatted values
  66. * @param {number} row row index
  67. * @param {number} col column index
  68. * @param {string} value cell value
  69. * @param {object} formatSpec format specification for formatting the value
  70. */
  71. GridDataProcessor.prototype.onCellDataChange = function onCellDataChange(row, col, value, formatSpec) {
  72. // CADBLOC-1398: summary value should show N/A when it is not available
  73. var rawValue = value && value.u ? value.u : value || NOT_AVAILABLE;
  74. this._onCellDataChange(row, col, Formatter.format(rawValue, formatSpec || null), rawValue);
  75. };
  76. GridDataProcessor.prototype._setUngroupedColumnsInfo = function _setUngroupedColumnsInfo() {
  77. for (var columnIndex = this._lastGroupedColumnIndex; columnIndex < this._numberOfColumns; columnIndex++) {
  78. var dataItem = this._columnDataItems[columnIndex];
  79. var isRankDataItem = dataItem.isRank && dataItem.isRank();
  80. var info = {
  81. 'dataItemUniqueId': dataItem.getId(),
  82. 'formatSpec': this._getSlotFormat(columnIndex),
  83. 'id': dataItem.getId(),
  84. 'index': columnIndex,
  85. 'aggregationType': dataItem.getAggregation(),
  86. 'addSummary': !isRankDataItem && this._columnSlot.getDataItemList()[this._getRealColumnIndex(columnIndex)].getAggregation() !== NONE
  87. };
  88. this._ungroupedColumnsInfo.push(info);
  89. }
  90. };
  91. GridDataProcessor.prototype._getSlotFormat = function _getSlotFormat(index) {
  92. var formatSpec = void 0;
  93. var dataItemList = this._columnSlot.getDataItemList();
  94. if (this._rankDataItems.length) {
  95. if (this._columnDataItems[index].isRank) {
  96. formatSpec = this._getRankFormat();
  97. } else {
  98. formatSpec = dataItemList[index - this._rankDataItems.length].getFormat();
  99. }
  100. } else {
  101. formatSpec = dataItemList[index].getFormat();
  102. }
  103. return formatSpec;
  104. };
  105. GridDataProcessor.prototype._generateNewDataTableWithOrder = function _generateNewDataTableWithOrder() {
  106. if (this._doSummary) {
  107. return this._preProcessedData.length ? this._preProcessedData : this._rawData;
  108. }
  109. for (var index = 0; index < this._rawData.length; index++) {
  110. var queryDataRow = this._rawData[index];
  111. var matchingIndex = -1;
  112. for (var i = 0; i < this._preProcessedData.length; i++) {
  113. var preProcessedDataRow = this._preProcessedData[i];
  114. if (!preProcessedDataRow) {
  115. continue;
  116. }
  117. if (this._rankDataItems.length > 0) {
  118. // in this scenario, only comparing edge rank value is sufficient since column index does not change and rank info is unique
  119. var preProcessedDataRowEdgeValue = preProcessedDataRow[0] && preProcessedDataRow[0].value;
  120. if (!preProcessedDataRowEdgeValue) {
  121. continue;
  122. }
  123. var queryDataRowEdgetRank = queryDataRow[0].value;
  124. if (!queryDataRowEdgetRank) {
  125. continue;
  126. }
  127. var preProcessedDataRowEdgeRank = preProcessedDataRowEdgeValue.deco && preProcessedDataRowEdgeValue.deco.rank;
  128. if (!preProcessedDataRowEdgeRank) {
  129. continue;
  130. }
  131. if (preProcessedDataRowEdgeRank === queryDataRowEdgetRank) {
  132. matchingIndex = i;
  133. break;
  134. }
  135. } else {
  136. // in this scenario, we need to compare the dataPoint value
  137. var preProcessedDataRowValue = preProcessedDataRow.map(function (item) {
  138. return item.value && _typeof(item.value.value) !== UNDEFINED ? item.value.value : item.value;
  139. });
  140. var queryDataRowValue = [];
  141. for (var j = 0; j < queryDataRow.length; j++) {
  142. var row = queryDataRow[j];
  143. if (Array.isArray(row)) {
  144. for (var k = 0; k < row.length; k++) {
  145. if (_typeof(row[k].value) !== UNDEFINED) {
  146. queryDataRowValue.push(row[k].value);
  147. }
  148. }
  149. } else if (_typeof(row.value) !== UNDEFINED) {
  150. queryDataRowValue.push(row.value);
  151. }
  152. }
  153. if (ArrayUtils.isArrayInArray(queryDataRowValue, preProcessedDataRowValue)) {
  154. matchingIndex = i;
  155. break;
  156. }
  157. }
  158. }
  159. if (matchingIndex !== -1) {
  160. this._rawData[index] = this._preProcessedData[matchingIndex];
  161. this._preProcessedData[matchingIndex] = null;
  162. }
  163. }
  164. return this._rawData;
  165. };
  166. GridDataProcessor.prototype.getData = function getData() {
  167. this._rawData = QueryResultDataUtils.getResolvedDataRows(this._queryResult);
  168. if (this._exceedsColumnThreshold) {
  169. // if ranking is applied or summary is disabled, we should respect the sort order of the query result
  170. return Promise.resolve(this._rawData);
  171. } else {
  172. // Promise-ifying this as a chain to allow time between the expensive steps for
  173. // the rest of the UI to react. Long-running
  174. return Promise.resolve().then(this._setUngroupedColumnsInfo.bind(this)).then(this._generateIntermediateObjects.bind(this)).then(this._generateNewDataTableWithSummaries.bind(this)).then(this._generateNewDataTableWithOrder.bind(this));
  175. }
  176. };
  177. /*
  178. * Generates grouped columns traversal object and a values map containing summaries.
  179. */
  180. GridDataProcessor.prototype._generateIntermediateObjects = function _generateIntermediateObjects() {
  181. //an intermediate object which the iterator will traverse to get grouped column and summaries info
  182. //used to generate the keys for indexing into the values map.
  183. var dataRowCount = this._queryResult.getRowCount();
  184. var container = {};
  185. var attributeRowValues = void 0;
  186. for (var rowIdx = 0; rowIdx < dataRowCount; rowIdx++) {
  187. attributeRowValues = this._getAttributeValuesInTheRow(rowIdx);
  188. this._addRowValuesToGroupedItemsContainer(container, attributeRowValues, rowIdx);
  189. this._generateIntermediateObjectsForUngroupedColumns(attributeRowValues, rowIdx);
  190. }
  191. return this._addSortKeysToContainer(container);
  192. };
  193. GridDataProcessor.prototype._generateIntermediateObjectsForUngroupedColumns = function _generateIntermediateObjectsForUngroupedColumns(attributeRowValues, rowIdx) {
  194. var ungroupedColumnInfo = void 0,
  195. value = void 0,
  196. rowValue = void 0,
  197. cellIdx = void 0,
  198. key = void 0,
  199. useValue = void 0,
  200. end = void 0,
  201. summaryKey = void 0,
  202. currentSummaryValue = void 0;
  203. for (var idx = 0, j = this._lastGroupedColumnIndex; j < this._numberOfColumns; j++, idx++) {
  204. ungroupedColumnInfo = this._ungroupedColumnsInfo[idx];
  205. value = this._getCellValue(rowIdx, j);
  206. cellIdx = rowIdx + ':' + j;
  207. if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) !== UNDEFINED) {
  208. useValue = getUseValue(value);
  209. if ((typeof useValue === 'undefined' ? 'undefined' : _typeof(useValue)) !== UNDEFINED && useValue !== null) {
  210. rowValue = attributeRowValues.concat(ungroupedColumnInfo.dataItemUniqueId);
  211. key = getKeys(rowValue);
  212. this._valuesMap[key] = { value: value, cellIdx: cellIdx };
  213. if (ungroupedColumnInfo.addSummary && ungroupedColumnInfo.aggregationType) {
  214. var keyUIds = {
  215. summaryKeys: [],
  216. oCategoryAndTupleUIds: {},
  217. isLocalSummary: this._isLocalSummary(ungroupedColumnInfo.aggregationType)
  218. };
  219. // groups not including the last attribute and measure
  220. end = rowValue.length - 2;
  221. for (; end >= 0; --end) {
  222. rowValue[end] = SUMMARY_ID;
  223. summaryKey = getKeys(rowValue);
  224. keyUIds.summaryKeys.push(summaryKey);
  225. var categoryAndTupleUId = {
  226. categoryUIds: [],
  227. tupleUIds: []
  228. };
  229. if (!(keyUIds.isLocalSummary || this.summaryNotSupported || keyUIds.oCategoryAndTupleUIds[summaryKey])) {
  230. for (var i = 0; i < this._columnDataItems.length; i++) {
  231. if (_typeof(rowValue[i]) === OBJECT) {
  232. categoryAndTupleUId.categoryUIds.push(this._columnDataItems[i].getId());
  233. categoryAndTupleUId.tupleUIds.push(rowValue[i].value);
  234. }
  235. }
  236. }
  237. keyUIds.oCategoryAndTupleUIds[summaryKey] = categoryAndTupleUId;
  238. currentSummaryValue = this._valuesMap[summaryKey];
  239. if (!currentSummaryValue && ungroupedColumnInfo) {
  240. currentSummaryValue = new Summary(ungroupedColumnInfo, keyUIds.isLocalSummary);
  241. if (!this.summaryNotSupported && !keyUIds.isLocalSummary) {
  242. // Add call back context to update values once resolved
  243. currentSummaryValue.addCallBackContext({
  244. categoryUIds: keyUIds.oCategoryAndTupleUIds[summaryKey].categoryUIds,
  245. tupleUIds: keyUIds.oCategoryAndTupleUIds[summaryKey].tupleUIds,
  246. cbOnCellDataChange: this.onCellDataChange.bind(this),
  247. cbGetSummary: this.summaryAPI ? this.summaryAPI.getSummary.bind(this.summaryAPI) : undefined,
  248. cbAddSummaryValuePromise: this.cacheSummaryValuePromise.bind(this)
  249. });
  250. }
  251. }
  252. if (currentSummaryValue) {
  253. currentSummaryValue.addValue(useValue);
  254. }
  255. this._valuesMap[summaryKey] = currentSummaryValue;
  256. }
  257. }
  258. }
  259. }
  260. }
  261. };
  262. /**
  263. * Retrieve a cell value for grid
  264. *
  265. * @param {number} rowIdx - the row index value
  266. * @param {number} columnIdx - the column index value
  267. *
  268. * @return {object} the cell value object
  269. */
  270. GridDataProcessor.prototype._getCellValue = function _getCellValue(rowIdx, columnIdx) {
  271. var value = this._queryResult.getValue(rowIdx, columnIdx);
  272. return value instanceof Array && !!value.length ? value[0] : value;
  273. };
  274. GridDataProcessor.prototype._getAttributeValuesInTheRow = function _getAttributeValuesInTheRow(rowIdx) {
  275. var attributeRowValues = [];
  276. for (var colIdx = 0; colIdx < this._numberOfColumns; colIdx++) {
  277. if (!this._factColumnsMap[colIdx]) {
  278. attributeRowValues.push(this._getCellValue(rowIdx, colIdx));
  279. }
  280. }
  281. return attributeRowValues;
  282. };
  283. /**
  284. * Returns a new object with Summary as the last item to be used for iterating
  285. */
  286. GridDataProcessor.prototype._addSortKeysToContainer = function _addSortKeysToContainer(edge) {
  287. var hasSummary = false;
  288. var newEdgeObject = {
  289. edge: edge,
  290. sortedKeys: []
  291. };
  292. for (var key in edge) {
  293. if (edge.hasOwnProperty(key)) {
  294. if (key === SUMMARY_ID) {
  295. hasSummary = true;
  296. } else {
  297. newEdgeObject.sortedKeys.push(key);
  298. var nestedEdges = edge[key].nestedEdges;
  299. if (nestedEdges) {
  300. edge[key].nestedEdges = this._addSortKeysToContainer(nestedEdges);
  301. }
  302. }
  303. }
  304. }
  305. if (hasSummary) {
  306. newEdgeObject.sortedKeys.push(SUMMARY_ID);
  307. var _nestedEdges = edge[SUMMARY_ID].nestedEdges;
  308. if (_nestedEdges) {
  309. edge[SUMMARY_ID].nestedEdges = this._addSortKeysToContainer(_nestedEdges);
  310. }
  311. }
  312. return newEdgeObject;
  313. };
  314. GridDataProcessor.prototype._addRowValuesToGroupedItemsContainer = function _addRowValuesToGroupedItemsContainer(container, attributeRowValues, rowIndex, columnIndex, ancestor) {
  315. var attributeRowValueIndex = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
  316. columnIndex = columnIndex || 0;
  317. if (attributeRowValues && attributeRowValues.length > 0) {
  318. var rowValue = attributeRowValues[attributeRowValueIndex].value;
  319. if (!container[rowValue]) {
  320. container[rowValue] = {
  321. value: attributeRowValues[attributeRowValueIndex],
  322. level: columnIndex,
  323. cellIdx: rowIndex + ':' + columnIndex
  324. };
  325. if (this._doSummary) {
  326. this._addSummaryToGroupedItemsContainer(container, columnIndex, ancestor);
  327. }
  328. }
  329. var nextAttributeRowValueIndex = attributeRowValueIndex + 1;
  330. if (nextAttributeRowValueIndex < attributeRowValues.length) {
  331. if (!container[rowValue].nestedEdges) {
  332. container[rowValue].nestedEdges = {};
  333. }
  334. this._addRowValuesToGroupedItemsContainer(container[rowValue].nestedEdges, attributeRowValues, rowIndex, columnIndex + 1, rowValue, nextAttributeRowValueIndex);
  335. }
  336. }
  337. };
  338. GridDataProcessor.prototype._addSummaryToGroupedItemsContainer = function _addSummaryToGroupedItemsContainer(container, columnIndex, ancestor, bIsOverallSummary) {
  339. var isOverallSummary = bIsOverallSummary || columnIndex === 0;
  340. if (columnIndex < this._numberOfAttributeColumns) {
  341. var summaryNode = {
  342. isSummary: true,
  343. level: columnIndex,
  344. isOverallSummary: isOverallSummary,
  345. summaryOf: isOverallSummary ? OVERALL : ancestor
  346. };
  347. container[SUMMARY_ID] = summaryNode;
  348. if (columnIndex < this._numberOfAttributeColumns - 1) {
  349. //special handling for overall summary in order for the crosstab to render the summary column correctly
  350. //need to add as many summary as nested levels
  351. summaryNode.nestedEdges = {};
  352. this._addSummaryToGroupedItemsContainer(summaryNode.nestedEdges, columnIndex + 1, ancestor, isOverallSummary);
  353. }
  354. }
  355. };
  356. /**
  357. * caching the summary value promise
  358. *
  359. * @param {object} summary value promise; when resolved the summary value is available
  360. */
  361. GridDataProcessor.prototype.cacheSummaryValuePromise = function cacheSummaryValuePromise(promise) {
  362. this._summaryValuePromises.push(promise);
  363. };
  364. /**
  365. * Returns a promise indicating whether all summaries get in the grid
  366. */
  367. GridDataProcessor.prototype.whenAllSummaryValuesAreReady = function whenAllSummaryValuesAreReady() {
  368. return Promise.all(this._summaryValuePromises);
  369. };
  370. GridDataProcessor.prototype._getSummaryKeys = function _getSummaryKeys(attributeRowValues) {
  371. var summaryKeys = {};
  372. //rowStart/rowEnd indicates starting/ending position in the originalDimension array that contains row info
  373. var rowEnd = this._numberOfAttributeColumns - 1;
  374. for (var rowStart = 0; rowEnd >= rowStart; --rowEnd) {
  375. attributeRowValues[rowEnd] = SUMMARY_ID;
  376. summaryKeys[getKeys(attributeRowValues)] = true;
  377. }
  378. return Object.keys(summaryKeys);
  379. };
  380. GridDataProcessor.prototype._generateNewDataTableWithSummaries = function _generateNewDataTableWithSummaries(groupedColumnsTraversalObject) {
  381. var dataWithSummary = [];
  382. var nextGroupedItem = void 0;
  383. var dataTableRow = void 0;
  384. var rowIndex = 0;
  385. var groupedColumnsRowIterator = new GridEdgeIterator(groupedColumnsTraversalObject, undefined);
  386. while (nextGroupedItem = groupedColumnsRowIterator.next()) {
  387. dataTableRow = [];
  388. this._addGroupedItemsToRow(dataTableRow, nextGroupedItem);
  389. this._addValuesToRow(dataTableRow, nextGroupedItem, ++rowIndex);
  390. dataWithSummary.push(dataTableRow);
  391. }
  392. this._preProcessedData = dataWithSummary;
  393. };
  394. GridDataProcessor.prototype._addGroupedItemsToRow = function _addGroupedItemsToRow(dataTableRow, firstGroupedItem) {
  395. var nextColumn = firstGroupedItem;
  396. var columnIndex = 0;
  397. while (nextColumn && columnIndex < this._lastGroupedColumnIndex) {
  398. dataTableRow.push(nextColumn);
  399. nextColumn = nextColumn.subCategory;
  400. columnIndex++;
  401. }
  402. };
  403. /*
  404. * This function adds fact value or attribute value which is not grouped.
  405. */
  406. GridDataProcessor.prototype._addValuesToRow = function _addValuesToRow(dataTableRow, firstGroupedItem, rowIndex) {
  407. for (var columnIndex = 0; columnIndex < this._ungroupedColumnsInfo.length; columnIndex++) {
  408. var nextColumn = this._ungroupedColumnsInfo[columnIndex];
  409. var key = getKeys(firstGroupedItem.descendant_or_self, nextColumn.dataItemUniqueId);
  410. var valueObj = this._valuesMap[key] || null;
  411. var data = null;
  412. var rawValue = null;
  413. if (valueObj) {
  414. if (valueObj.getSummarizedValue) {
  415. if (!valueObj._isLocalSummary) {
  416. data = valueObj.getSummarizedValue(rowIndex, nextColumn.index);
  417. } else if (valueObj.getValue() !== null) {
  418. rawValue = valueObj.getValue();
  419. data = Formatter.formatNull(rawValue, valueObj._formatSpec || null);
  420. } else {
  421. data = valueObj.value;
  422. }
  423. } else {
  424. data = valueObj.value;
  425. }
  426. }
  427. var result = data;
  428. if (!result) {
  429. //for summary of attribute column which is ungrouped
  430. if (firstGroupedItem.isSummary && !nextColumn.aggregationType) {
  431. result = ' ';
  432. }
  433. }
  434. dataTableRow.push({
  435. isSummary: firstGroupedItem.isSummary,
  436. value: result,
  437. rawValue: rawValue,
  438. key: key,
  439. cellIdx: valueObj ? valueObj.cellIdx : null,
  440. isOverallSummary: firstGroupedItem.isOverallSummary
  441. });
  442. }
  443. };
  444. GridDataProcessor.prototype._isLocalSummary = function _isLocalSummary(aggregationType) {
  445. return SummaryTypes.indexOf(aggregationType) === -1;
  446. };
  447. return GridDataProcessor;
  448. }();
  449. return GridDataProcessor;
  450. });
  451. //# sourceMappingURL=GridDataProcessor.js.map