VisQueryDataItemBuilder.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2017, 2019
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. *
  7. * VisQueryDataItemBuilder
  8. * The VisQueryDataItemBuilder is responsible for turning the information in a dataItem into a dss query.
  9. */
  10. define(['underscore', '../../../lib/@waca/core-client/js/core-client/ui/core/Class', './postprocess/LocalAggregateSortInfo'], function (_, Class, LocalAggregateSortInfo) {
  11. 'use strict';
  12. // noinspection JSAnnotator
  13. /**
  14. * INTENT: VisQueryDataItemBuilder is used to construct a dataItem in the form needed by the query.
  15. * building the dataItem involves a number of 'on the fly' steps such as:
  16. * inserting the appropriate initial selection based on the current drill state,
  17. * deciding if a filter should be a dataItem selection
  18. * ensuring that selections are performed in a predictable order (initial selection (root members or drill members), keep/remove, topN, sort)
  19. */
  20. var MAX_DECIMAL_PLACES = 2;
  21. var VisQueryDataItemBuilder = Class.extend({
  22. init: function init(attributes) {
  23. VisQueryDataItemBuilder.inherited('init', this, arguments);
  24. this.visAPI = attributes.visAPI;
  25. this._querySlotSortPriority = 0;
  26. this._localAggregateSortInfo = attributes.localAggregateSortInfo || new LocalAggregateSortInfo(attributes.visAPI);
  27. },
  28. setQuerySlotSortPriority: function setQuerySlotSortPriority(priority) {
  29. this._querySlotSortPriority = priority;
  30. },
  31. /**
  32. * @return serverSort boolean true to use server sort.
  33. */
  34. getServerSort: function getServerSort() {
  35. return this._serverSort;
  36. },
  37. /**
  38. * @param serverSort true to use server sort.
  39. */
  40. setServerSort: function setServerSort(serverSort) {
  41. this._serverSort = serverSort;
  42. },
  43. /*
  44. * @private
  45. * @return {Array} An array of Data Item objects assigned to a single slot.
  46. **/
  47. build: function build(slotAPI, dataItemAPI, queryContext, dataItemIdx) {
  48. var oDataItemJSON = dataItemAPI.toQueryJSON();
  49. oDataItemJSON.layerId = slotAPI.getLayerId();
  50. if (slotAPI.getFinalSlotType(dataItemIdx) === 'ordinal') {
  51. // If the final slot type is ordinal, it means we are querying the data item
  52. // in a ordinal form, which in this case we want to respect the aggregation type
  53. // except 'none'
  54. oDataItemJSON.aggregate = slotAPI.getFinalAggregationType(dataItemIdx);
  55. } else {
  56. delete oDataItemJSON.aggregate;
  57. }
  58. var finalSelection = this._buildFinalItemSelections(slotAPI, dataItemAPI, queryContext, oDataItemJSON);
  59. if (finalSelection.length) {
  60. oDataItemJSON.selection = finalSelection;
  61. } else if (oDataItemJSON.selection && oDataItemJSON.selection.length === 0) {
  62. // avoid having an empty selection in query spec
  63. delete oDataItemJSON.selection;
  64. }
  65. var binning = this._buildBinningSpec(dataItemAPI);
  66. //binned dataItem type will be attribute, so if it has aggregate we need to remove it.
  67. // binningDefinition is part of toQueryJSON (need for copy/past), but dss query has different format
  68. if (binning) {
  69. //remove old selection, since it may have top/bottom
  70. if (!finalSelection.length) {
  71. delete oDataItemJSON.selection;
  72. }
  73. delete oDataItemJSON.binningDefinition;
  74. delete oDataItemJSON.aggregate;
  75. oDataItemJSON.binning = binning;
  76. }
  77. return oDataItemJSON;
  78. },
  79. _buildBinningSpec: function _buildBinningSpec(dataItemAPI) {
  80. var binningAPI = dataItemAPI.getBinningAPI();
  81. if (binningAPI) {
  82. var format = dataItemAPI.getFormat();
  83. var maxDecPlaces = format && (format.formatSpec.maximumFractionDigits || format.formatSpec.maximumFractionDigits === 0) ? format.formatSpec.maximumFractionDigits : MAX_DECIMAL_PLACES;
  84. binningAPI.setMaxDecPlaces(maxDecPlaces);
  85. return binningAPI.getBinning();
  86. }
  87. return null;
  88. },
  89. //unless selections are pre-defined and locked (WA2 upgrade), add selection actions in a specific order
  90. //to ensure that the results are predictable and explainable!
  91. _buildFinalItemSelections: function _buildFinalItemSelections(slotAPI, dataItemAPI, queryContext, oDataItemJSON) {
  92. //TODO: For WA2, the dataItemSelections will be locked. In that case we need to honour the selections from the dataItem 'as is'.
  93. if (!dataItemAPI.isBinned()) {
  94. //By default, return selections in the order agreed to by PM.
  95. return this._buildDrillStateForHierarchyOrSet(dataItemAPI, queryContext, oDataItemJSON).concat(this._buildItemSelectionsFromFilterCollections(dataItemAPI, queryContext), this._buildTopBottom(dataItemAPI), this._buildSort(slotAPI, dataItemAPI));
  96. } else {
  97. return this._buildItemSelectionsFromFilterCollections(dataItemAPI, queryContext).concat(this._buildSort(slotAPI, dataItemAPI));
  98. }
  99. },
  100. //For hierarchies or OLAP set, build one of root members, children of single root or children of current drill member.
  101. _buildDrillStateForHierarchyOrSet: function _buildDrillStateForHierarchyOrSet(dataItemAPI, queryContext, oDataItemJSON) {
  102. var localFilters = queryContext && queryContext.entities && queryContext.entities.localFilters;
  103. var matchingLocalFilterEntry = localFilters && localFilters.getFilterEntry({ id: dataItemAPI.getItemId(), columnId: dataItemAPI.getItemId() });
  104. var overrideHierarchyDefaultSelection = matchingLocalFilterEntry && matchingLocalFilterEntry.overrideHierarchyDefaultSelection;
  105. var selection = [];
  106. if (dataItemAPI.isHierarchy() || dataItemAPI.isNamedSet()) {
  107. var existingDrillSelection = dataItemAPI.getDrillSelection();
  108. if (existingDrillSelection) {
  109. if (dataItemAPI.isNamedSet()) {
  110. oDataItemJSON.itemId = dataItemAPI.getRefToHierarchy();
  111. }
  112. selection.push(existingDrillSelection);
  113. } else if (dataItemAPI.isHierarchy() && !overrideHierarchyDefaultSelection) {
  114. if (dataItemAPI.isSingleRootHierarchy()) {
  115. selection.push({
  116. 'operation': 'add',
  117. 'children': dataItemAPI.getRootMember()
  118. });
  119. } else {
  120. selection.push({
  121. 'operation': 'add',
  122. 'rootMembers': true
  123. });
  124. }
  125. }
  126. }
  127. return selection;
  128. },
  129. _convertFilterOperatorToSetOperation: function _convertFilterOperatorToSetOperation(operator) {
  130. switch (operator) {
  131. case 'in':
  132. case 'containsignorecase':
  133. return 'keep';
  134. case 'notin':
  135. return 'remove';
  136. default:
  137. return;
  138. }
  139. },
  140. _convertFilterEntryToSelection: function _convertFilterEntryToSelection(filterEntry) {
  141. var discreteOperation = this._convertFilterOperatorToSetOperation(filterEntry.operator);
  142. var result = null;
  143. if (discreteOperation) {
  144. var valuesArray = _.map(filterEntry.values.models, function (valueEntry) {
  145. return valueEntry.value.u ? valueEntry.value.u : valueEntry.value;
  146. });
  147. result = {
  148. operation: discreteOperation
  149. };
  150. if (filterEntry.operator === 'containsignorecase') {
  151. result.filter = {
  152. operator: filterEntry.operator,
  153. values: valuesArray
  154. };
  155. } else {
  156. result.set = valuesArray;
  157. }
  158. }
  159. return result;
  160. },
  161. _convertItemFiltersToSelections: function _convertItemFiltersToSelections(filters, queryContext, selections) {
  162. var _this = this;
  163. queryContext.filtersConvertedToDataItemSelections = queryContext.filtersConvertedToDataItemSelections || [];
  164. _.each(filters, function (filterEntry) {
  165. if (filterEntry && filterEntry.values.models.length > 0 && !filterEntry.conditions) {
  166. var selectionItem = _this._convertFilterEntryToSelection(filterEntry);
  167. if (selectionItem) {
  168. selections.push(selectionItem);
  169. }
  170. //Track which filters were converted to dataItemSelections (so we don't filter on them as well!)
  171. queryContext.filtersConvertedToDataItemSelections.push(filterEntry);
  172. }
  173. });
  174. },
  175. /**
  176. * Convert Filters into Selections.
  177. *
  178. * In cases where the dataItem has facetData enabled (most of the time)
  179. * AND there is a filter on that dataItem, convert the filter to a set selection on the dataItem.
  180. * Note: this ensures that a contextual topN on an item will return N members from the remaining set
  181. * EXAMPLE: top2 Products based on Revenue
  182. * Product1 $1000
  183. * Product2 $2000
  184. * Product3 $1500
  185. * Product4 $1700
  186. * Keep Product1, Product3, Product4
  187. * setSelect Result = $1700, $1500 (the top2 of the selected set ==> which is what most people would expect)
  188. * Filter Result = $1700 (because only Product4 is in the Top2 of the entire set)
  189. *
  190. **/
  191. _buildItemSelectionsFromFilterCollections: function _buildItemSelectionsFromFilterCollections(dataItemAPI, queryContext) {
  192. var dataItemId = dataItemAPI.getItemId();
  193. var localFiltersAPI = queryContext && queryContext.entities && queryContext.entities.localFilters;
  194. var searchFiltersAPI = queryContext && queryContext.entities && queryContext.entities.searchFilters;
  195. var existingSetSelection = dataItemAPI.setSelectionsToQueryJSON();
  196. var selections = existingSetSelection ? existingSetSelection : [];
  197. var isAuxQuery = queryContext && queryContext.auxQuery;
  198. if (!isAuxQuery && dataItemAPI.isDimensional()) {
  199. if (localFiltersAPI) {
  200. this._convertItemFiltersToSelections(localFiltersAPI.getFilterEntries({ id: dataItemId }), queryContext, selections);
  201. }
  202. if (searchFiltersAPI) {
  203. this._convertItemFiltersToSelections(searchFiltersAPI.getFilterEntries({ id: dataItemId }), queryContext, selections);
  204. }
  205. }
  206. return selections;
  207. },
  208. _buildTopBottom: function _buildTopBottom(dataItemAPI) {
  209. var selection = [];
  210. var topBottom = dataItemAPI.getTopBottomSelection();
  211. if (topBottom) {
  212. selection.push(topBottom);
  213. }
  214. return selection;
  215. },
  216. _buildSort: function _buildSort(slotAPI, dataItemAPI) {
  217. var bClientSortEnabled = this.getServerSort() ? false : this._isClientSideSortEnabledForDataItem(dataItemAPI);
  218. var selection = [];
  219. if (!bClientSortEnabled) {
  220. // 1. Sort from data item.
  221. var sort = dataItemAPI.getSort();
  222. var sortObj = sort && sort.type ? sort : null;
  223. var ignoreSlotSort = this.visAPI.isIgnoreDefaultSlotSort(dataItemAPI);
  224. // Preserve the hierarchical order for Olap column.
  225. if (!sortObj && !ignoreSlotSort) {
  226. sortObj = dataItemAPI.getDefaultSort(slotAPI, dataItemAPI);
  227. if (sortObj) {
  228. sortObj.priority = this._querySlotSortPriority++;
  229. }
  230. }
  231. if (sortObj) {
  232. var sortSelection = {
  233. 'operation': 'order',
  234. 'sort': _.pick(sortObj, 'type', 'by', 'priority')
  235. };
  236. if (sortObj.context) {
  237. sortSelection.context = [{
  238. itemId: sortObj.context
  239. }];
  240. }
  241. selection = [sortSelection];
  242. }
  243. }
  244. return selection;
  245. },
  246. /**
  247. * If there will be any post processing client side sorting on the dataItem
  248. * optimize to not sort on server as well.
  249. */
  250. _isClientSideSortEnabledForDataItem: function _isClientSideSortEnabledForDataItem(dataItemAPI) {
  251. var localSortInfo = this._localAggregateSortInfo.getSortInfo();
  252. if (localSortInfo) {
  253. return localSortInfo.aggregateColumn === dataItemAPI.getUniqueId();
  254. }
  255. return false;
  256. }
  257. });
  258. return VisQueryDataItemBuilder;
  259. });
  260. //# sourceMappingURL=VisQueryDataItemBuilder.js.map