ResultDataReader.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: BI Dashboard
  6. *| (C) Copyright IBM Corp. 2018, 2019
  7. *|
  8. *| US Government Users Restricted Rights - Use, duplication or disclosure
  9. *| restricted by GSA ADP Schedule Contract with IBM Corp.
  10. *+------------------------------------------------------------------------+
  11. */
  12. define(['../../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../../apiHelpers/SlotAPIHelper', 'dashboard-analytics/widgets/livewidget/query/QueryResultData'], function (Class, SlotAPIHelper, QueryResultData) {
  13. 'use strict';
  14. /**
  15. * The ResultDataReader is a helper for the ResultsDataReaderTask but works at a single dataset level.
  16. * It is similar to a PostProcessor except that its results
  17. * are not fed into the main data stream for rendering.
  18. * Each feature that has the "ResultDataReaderPlugin" FeatureTag defined will be Called
  19. * at standard points through the data.... onDataStart, onDataRow, onDataDone.
  20. * (A feature can choose to implement one or all of those apis)
  21. **/
  22. var ResultDataReader = Class.extend({
  23. /**
  24. * @Constructor
  25. * @param {Object} widgetApi - The widget.
  26. * @param {Object} cbCreateQueryResultData (optional) - define a query result data API for testing etc.
  27. */
  28. init: function init(widgetApi, visualization, cbCreateQueryResultData) {
  29. ResultDataReader.inherited('init', this, arguments);
  30. this.visualization = visualization;
  31. this._createQueryResultData = cbCreateQueryResultData || this._createQueryResultData;
  32. //Get all available features that are identified as resultDataReaderPlugins.
  33. //Since we ask for this on every read of the data, we can limit it to enabled features.
  34. this.plugins = widgetApi.getMatchingFeatures('ResultDataReaderPlugin', /*bEnabledOnly*/true);
  35. },
  36. _createQueryResultData: function _createQueryResultData(resultData) {
  37. return new QueryResultData(resultData);
  38. },
  39. hasPlugins: function hasPlugins() {
  40. return this.plugins.length > 0;
  41. },
  42. onNewResults: function onNewResults(resultData) {
  43. this.plugins.forEach(function (plugin) {
  44. return plugin.onNewResults && plugin.onNewResults(resultData);
  45. });
  46. },
  47. /**
  48. * Process the supplied resultData (postProcessed query result for a single DSS query.)
  49. * @param queryResult - the resultData
  50. * @param dataViewId - the data view id (also known as the layerId)
  51. * @param renderContext - the renderContext (which flows through the renderSequnce)
  52. * @returns An array of 1 or more promises which is resolved when all work for all plugins is complete.
  53. */
  54. processData: function processData(queryResult, dataViewId, renderContext) {
  55. //Wrap the processed data rows in a promise.
  56. if (this.plugins.length) {
  57. return this._readResultData(queryResult, dataViewId, renderContext);
  58. }
  59. return [Promise.resolve({ hasPlugins: false })];
  60. },
  61. //TODO: queryResultParm is normally a queryResult API but can be a query result object if the new query api is disabled...
  62. _readResultData: function _readResultData(queryResultParm, dataViewId, renderContext) {
  63. var useNewQueryApi = renderContext && renderContext.useAPI;
  64. var queryResult = useNewQueryApi ? queryResultParm
  65. //TODO: queryResult can be the old QueryResultData object if the new query api is disabled...
  66. : queryResultParm._resultData && this._createQueryResultData(queryResultParm._resultData);
  67. var rowHeaders = this._buildRowHeaders(dataViewId);
  68. var rowCount = useNewQueryApi ? queryResult.getRowCount() : queryResult.getDatapointCount();
  69. this.plugins.forEach(function (plugin) {
  70. return plugin.onDataStart && plugin.onDataStart(queryResult, rowHeaders);
  71. });
  72. for (var rowIdx = 0; rowIdx < rowCount; rowIdx += rowHeaders.valueSlotMeasureCount) {
  73. this.plugins.forEach(function (plugin) {
  74. return plugin.onDataRow && plugin.onDataRow(rowIdx);
  75. });
  76. }
  77. var promises = [];
  78. this.plugins.forEach(function (plugin) {
  79. if (plugin.onDataDone) {
  80. //For a given plugin, onDataDone may or may not return a promise.
  81. //If any plugins do, we need to wait for them.
  82. var onDataDonePromise = plugin.onDataDone(renderContext);
  83. if (onDataDonePromise) {
  84. promises.push(onDataDonePromise);
  85. }
  86. }
  87. });
  88. return promises.length ? promises : [Promise.resolve({})];
  89. },
  90. _buildRowHeaders: function _buildRowHeaders(dataViewId) {
  91. var measureItemHeaders = []; //measure is helpful for understanding (its actually 'ordinal')
  92. var mappedSlots = this.visualization.getSlots().getMappedSlotList();
  93. var slotAPIs = dataViewId ? mappedSlots.filter(function (slotAPI) {
  94. return slotAPI.getDefinition().getDatasetIdList().indexOf(dataViewId) !== -1;
  95. }) : mappedSlots;
  96. var valueSlotMeasureCount = 1;
  97. //For any row of data, the categoryItemHeaders represents the set of categoryItemIds and the cell indexes where they are found.
  98. var categoryItemHeaders = {
  99. itemIds: [], //The itemIds of the categorical items.
  100. columnIndexes: [] //The column indexes where the categorical items occur (Note: when nested, 1 column index = 1 tuple)
  101. };
  102. //The first level result column index represents how a row of data is returned.
  103. //EXAMPLE
  104. //When a slot is stackable, all items in the slot are returned as a 'tuple' (as a sub-array of the firstLevelArray)
  105. //When a slot is not stackable, each item in the slot is returned under a separate first level array index.
  106. //slot0, slot1 is stackable: [[2004, Summer], [Fax, Wednesday], 300]
  107. //slot0 is not stackable, slot1 is stackable...returns the same data as [[2004], [Summer], [Fax, Wednesday], 300]
  108. var firstLevelResultColumnIndex = 0;
  109. slotAPIs.forEach(function (slotAPI) {
  110. var isMultiMeasure = SlotAPIHelper.isMultiMeasuresValueSlot(slotAPI);
  111. //This flag signifies that there are multiple items in this slot under the same first level column.
  112. var isSlotStackable = slotAPI.isStacked() || isMultiMeasure;
  113. if (slotAPI.getDefinition().getType() === 'ordinal' && slotAPI.getDataItemList().length > 1 && isMultiMeasure) {
  114. //If there's an ordinal slot with more than 1 item assigned to it, it is the "multi-measure value" slot.
  115. //For this case, the pre-processor will create a virtual category to represent the seriesIndex
  116. //(one value per measure name) and the value that pairs with that name will be reported on that row.
  117. //ie: Year, Revenue, Cost becomes Year SERIES Value
  118. // 2007 10000 500 2007 Revenue 10000
  119. // 2007 Cost 500
  120. //Note that slotAPIs match the post-processed structure (values would be at slot index 2 (and that dataitem index))
  121. valueSlotMeasureCount = slotAPI.getDataItemList().length;
  122. }
  123. slotAPI.getDataItemList().forEach(function (dataItemAPI, dataItemIndex) {
  124. var theMultiMeasureValueIndex = isMultiMeasure ? dataItemIndex : 0;
  125. if (dataItemAPI.getType() === 'fact') {
  126. //Set up a measureItemHeaders for each ordinal.
  127. measureItemHeaders.push({
  128. dataItemUniqueId: dataItemAPI.getId(),
  129. index: firstLevelResultColumnIndex,
  130. valueItemIndex: theMultiMeasureValueIndex, //This will only be non-zero for the stacked-measures case
  131. aggregate: dataItemAPI.getAggregation(),
  132. multiMeasuresValueItem: isMultiMeasure
  133. });
  134. } else {
  135. categoryItemHeaders.itemIds.push(dataItemAPI.getColumnId());
  136. if (isSlotStackable) {
  137. //When collecting categories, getCellValue returns a tuple for stacked items.
  138. //Because of this, we only want the value index of the parent cell.
  139. //Unless this item does not have stackable items.
  140. if (dataItemIndex === 0) {
  141. categoryItemHeaders.columnIndexes.push(firstLevelResultColumnIndex);
  142. }
  143. } else {
  144. categoryItemHeaders.columnIndexes.push(firstLevelResultColumnIndex);
  145. }
  146. }
  147. //If the slot is not stackable (ie: table or crosstab), increment the column index for each item in the slot.
  148. firstLevelResultColumnIndex += !isSlotStackable ? 1 : 0;
  149. });
  150. //If the slot is stackable, increment the column index once per slot.
  151. firstLevelResultColumnIndex += isSlotStackable ? 1 : 0;
  152. });
  153. return {
  154. layerId: dataViewId,
  155. categoryItemHeaders: categoryItemHeaders,
  156. measureItemHeaders: measureItemHeaders,
  157. valueSlotMeasureCount: valueSlotMeasureCount
  158. };
  159. }
  160. });
  161. return ResultDataReader;
  162. });
  163. //# sourceMappingURL=ResultDataReader.js.map