VisQueryBuilder.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: Dashboard
  5. * (C) Copyright IBM Corp. 2013, 2019
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. *
  8. * VisQueryBuilder
  9. * The VisQueryBuilder is responsible for turning the information in the dataMappingManager into a DSS query.
  10. */
  11. define(['underscore', '../../../lib/@waca/core-client/js/core-client/ui/core/Class', './VisQueryDataItemBuilder', './QueryFilterSpec', './VisQueryDefinition'], function (_, Class, VisQueryDataItemBuilder, QueryFilterSpec, VisQueryDefinition) {
  12. 'use strict';
  13. var VisQueryBuilder = null; // class declaration
  14. VisQueryBuilder = Class.extend({
  15. init: function init(attributes) {
  16. console.debug('visQueryBuilder (lw) init');
  17. VisQueryBuilder.inherited('init', this, arguments);
  18. _.forEach(attributes, function (attribute, key) {
  19. this[key] = attribute;
  20. }.bind(this));
  21. this.visQueryDataItemBuilder = new VisQueryDataItemBuilder({ visAPI: attributes.visAPI });
  22. },
  23. /**
  24. * create a visualization query
  25. * @param queryContext - the context of the query.
  26. * @returns the queryDefinition in a form that can be executed.
  27. */
  28. create: function create(queryContext) {
  29. var aMappedLayers = [];
  30. var oLayers = this._getQueryLayers();
  31. if (oLayers) {
  32. _.each(oLayers.getLayers(), function (layer) {
  33. if (this.visAPI.isMappingComplete(layer.id)) {
  34. aMappedLayers.push(layer);
  35. }
  36. }.bind(this));
  37. }
  38. if (aMappedLayers.length === 0) {
  39. aMappedLayers.push(this.visAPI.getDefaultLayer());
  40. }
  41. queryContext.visibleItemsMap = this._createVisibleItemsMapFromLiveWidgetModel();
  42. var aDataItems = this._createQueryDataItemsFromLiveWidgetModel(queryContext);
  43. var aProjections = this._createQueryProjectionsFromLiveWidgetModel(queryContext);
  44. //Mapping of all the projected data items (necessary for local filters)
  45. queryContext.projectedDataItemsMap = this._createVisibleItemsMapFromLiveWidgetModel(true);
  46. //Remove aFields, it should not be needed.
  47. var aFields = this._createQueryDefinitionFieldsFromLiveWidgetModel();
  48. var aFilters = this.createLocalFilters(queryContext, aMappedLayers);
  49. var aQueryHints = this._createQueryHints(queryContext);
  50. var oVisQueryDefinition = new VisQueryDefinition({ 'layers': aMappedLayers,
  51. 'layeredEntities': {
  52. 'dataItems': aDataItems,
  53. 'projections': aProjections,
  54. 'fields': aFields,
  55. 'filters': aFilters
  56. },
  57. 'commonEntities': {
  58. 'queryHints': aQueryHints
  59. } });
  60. return oVisQueryDefinition;
  61. },
  62. /**
  63. * Turn on multiEdgeSort based on following rules:
  64. * 1: if slot is isMultiMeasuresSeries and is Rank, does not consider as edge
  65. * 2: if slot is isMultiMeasuresSeries and is stacked, considered as one edge
  66. * 3: if slot is stacked, it is considered as one edge
  67. * 4: if slot is of type ordinal, does not consider as edge
  68. * if number of edges is more than one, then turn on multiEdgeSort otherwise not
  69. * @param queryHints the queryHint for query
  70. * @return {boolean} true if number of edges to sort is more than 1.
  71. * @private
  72. */
  73. _queryHintMultiEdgeSort: function _queryHintMultiEdgeSort(queryHints) {
  74. var _this = this;
  75. var numberOfEdges = 0;
  76. if (queryHints && !queryHints.multiEdgeSort) {
  77. return false;
  78. }
  79. _.forEach(this.visAPI.getDataSlots(), function (slotAPI) {
  80. if (_this._canQuery(slotAPI) && slotAPI.getFinalSlotType() !== 'ordinal' && slotAPI.getDataItemAPIs().length > 0) {
  81. numberOfEdges++;
  82. }
  83. });
  84. return numberOfEdges > 1;
  85. },
  86. _createQueryHints: function _createQueryHints(queryContext) {
  87. var queryHints = {};
  88. if (queryContext.entities && queryContext.entities.definition && queryContext.entities.definition.queryHints) {
  89. _.extend(queryHints, queryContext.entities.definition.queryHints);
  90. }
  91. if (queryContext.entities.preferredModelItems && queryContext.entities.preferredModelItems.length) {
  92. queryHints.preferredModelItems = queryContext.entities.preferredModelItems;
  93. }
  94. // Multi edge sort is only required by the main data query
  95. // and is not required by auxiliary queries such as summary queries (for performance reasons)
  96. if (queryContext.auxQuery || !this._queryHintMultiEdgeSort(queryHints)) {
  97. delete queryHints.multiEdgeSort;
  98. }
  99. if (queryContext.renderContext && queryContext.renderContext.extraInfo && queryContext.renderContext.extraInfo.sender === 'realtimeRefresh') {
  100. // Set the dataCacheExpiry to 40% of the refresh time
  101. var refreshValue = queryContext.renderContext.extraInfo.queryRefresh && queryContext.renderContext.extraInfo.queryRefresh.value;
  102. var dataCacheExpiryValue;
  103. switch (queryContext.renderContext.extraInfo.queryRefresh && queryContext.renderContext.extraInfo.queryRefresh.unit) {
  104. case 'seconds':
  105. dataCacheExpiryValue = parseInt(refreshValue * 4 / 10) || 1;
  106. break;
  107. case 'minutes':
  108. dataCacheExpiryValue = parseInt(refreshValue * 60 * 4 / 10) || 1;
  109. break;
  110. case 'hours':
  111. dataCacheExpiryValue = parseInt(refreshValue * 3600 * 4 / 10) || 1;
  112. break;
  113. default:
  114. dataCacheExpiryValue = 1;
  115. }
  116. queryHints.dataCacheExpiry = dataCacheExpiryValue.toString();
  117. } else {
  118. switch (queryContext.entities && queryContext.entities.properties && queryContext.entities.properties.localCache) {
  119. case 'yes':
  120. queryHints.dataCacheExpiry = '3600';
  121. break;
  122. case 'no':
  123. queryHints.dataCacheExpiry = '0';
  124. break;
  125. default:
  126. }
  127. }
  128. return [queryHints];
  129. },
  130. //TODO: Remove the function and sub functions when 'fields' is not needed in the flow for Endor
  131. _createQueryDefinitionFieldsFromLiveWidgetModel: function _createQueryDefinitionFieldsFromLiveWidgetModel() {
  132. var fields = [];
  133. var slotAPIs = this._getQuerySlots();
  134. _.each(slotAPIs, function (slotAPI) {
  135. fields = fields.concat(this._slotToFields(slotAPI));
  136. }.bind(this));
  137. return fields;
  138. },
  139. _createQueryDataItemsFromLiveWidgetModel: function _createQueryDataItemsFromLiveWidgetModel(queryContext) {
  140. var slotAPIs = this._getQuerySlots();
  141. // Set slot sort priority to highest data items sort priority plus 1.
  142. var priority = 0;
  143. var aDataItems = [];
  144. _.each(slotAPIs, function (slotAPI) {
  145. _.each(slotAPI.getDataItemAPIs(), function (dataItemAPI) {
  146. var sortObj = dataItemAPI.getSort();
  147. if (sortObj && priority < sortObj.priority) {
  148. priority = sortObj.priority;
  149. }
  150. });
  151. });
  152. this.visQueryDataItemBuilder.setQuerySlotSortPriority(priority + 1);
  153. var multiMeasures = this.visAPI.findSlotDataItem(function (dataItemAPI) {
  154. return dataItemAPI.getItemId() === '_multiMeasuresSeries';
  155. });
  156. // If multi measures are dropped to one slot, should not do client (local) sort.
  157. var serverSort = multiMeasures ? true : false;
  158. this.visQueryDataItemBuilder.setServerSort(serverSort);
  159. _.each(slotAPIs, function (slotAPI) {
  160. aDataItems = aDataItems.concat(this._slotToDataItems(slotAPI, queryContext));
  161. }.bind(this));
  162. if (queryContext && queryContext.queryOptions) {
  163. // list of dataitems to be added to the generated dataitems list
  164. if (queryContext.queryOptions.extraDataItems) {
  165. aDataItems = aDataItems.concat(queryContext.queryOptions.extraDataItems);
  166. }
  167. // list of dataitem ids to be removed from the generated dataitems list
  168. if (typeof queryContext.queryOptions.filterDataItems === 'function') {
  169. aDataItems = _.filter(aDataItems, queryContext.queryOptions.filterDataItems);
  170. }
  171. }
  172. return aDataItems;
  173. },
  174. /**
  175. * @param {boolean} includeAllDataItems As long as it can query, it will add the data items
  176. * This is for local filters, where we need all the project data items to pass.
  177. */
  178. //Use the slots and assigned dataItems to create a map of all visible items in this visualization.
  179. _createVisibleItemsMapFromLiveWidgetModel: function _createVisibleItemsMapFromLiveWidgetModel(includeAllDataItems) {
  180. var visibleItemsMap = {};
  181. this.visAPI.eachSlotDataItem(function (dataItemAPI, slotAPI) {
  182. // Exclude ordinal data items in visibleItemsMap.
  183. // Brushing should be converted to a filter when data item is in an ordinal slot type.
  184. // Adding isLatLong conditon for Lat/Ling data. It's a special type in that it's technically treated as categorical slots.
  185. if (this._canQuery(slotAPI) && (slotAPI.isLatLong() || slotAPI.getType() !== 'ordinal' || includeAllDataItems)) {
  186. visibleItemsMap[dataItemAPI.getItemId()] = visibleItemsMap[dataItemAPI.getItemId()] || [];
  187. var visibleItemInfo = { dataItemAPI: dataItemAPI, layerId: slotAPI.getLayerId(), viewId: slotAPI.getViewId() };
  188. visibleItemsMap[dataItemAPI.getItemId()].push(visibleItemInfo);
  189. }
  190. }.bind(this));
  191. return visibleItemsMap;
  192. },
  193. _createQueryProjectionsFromLiveWidgetModel: function _createQueryProjectionsFromLiveWidgetModel(queryContext) {
  194. var aProjections = [];
  195. var slotAPIs = this._getQuerySlots();
  196. _.each(slotAPIs, function (slotAPI) {
  197. aProjections = aProjections.concat(this._slotToProjections(slotAPI, queryContext));
  198. }.bind(this));
  199. if (queryContext && queryContext.queryOptions) {
  200. // list of projections to be added to the generated projections list
  201. if (queryContext.queryOptions.extraProjections) {
  202. aProjections = aProjections.concat(queryContext.queryOptions.extraProjections);
  203. }
  204. // list of projection ids to be removed from the generated projections list
  205. if (typeof queryContext.queryOptions.filterProjections === 'function') {
  206. aProjections = _.filter(aProjections, queryContext.queryOptions.filterProjections);
  207. }
  208. }
  209. return aProjections;
  210. },
  211. _createQueryFilterSpec: function _createQueryFilterSpec(visibleItemsMap) {
  212. return new QueryFilterSpec(visibleItemsMap);
  213. },
  214. //@returns the categorical items for the specified layer
  215. _getCategoryItemsForLayer: function _getCategoryItemsForLayer(layerId) {
  216. var slotAPIs = this._getQuerySlots();
  217. var catSlotsForLayer = _.filter(slotAPIs, function (slotAPI) {
  218. return slotAPI.getLayerId() === layerId && slotAPI.getFinalSlotType() === 'category';
  219. });
  220. //Extract the dataItemId's for each slot in this layer.
  221. var itemIdsForLayer = [];
  222. _.each(catSlotsForLayer, function (categorySlot) {
  223. _.each(categorySlot.getDataItemAPIs(), function (dataItemAPI) {
  224. itemIdsForLayer.push(dataItemAPI.getItemId());
  225. });
  226. });
  227. return itemIdsForLayer;
  228. },
  229. //@returns true if categorySet1 and categorySet2 are different (in both directions)
  230. _categoriesAreDifferent: function _categoriesAreDifferent(categorySet1, categorySet2) {
  231. return _.difference(categorySet1 || [], categorySet2 || []).length > 0 || _.difference(categorySet2 || [], categorySet1 || []).length > 0;
  232. },
  233. /**
  234. * Return the local query filters
  235. * @param queryContext
  236. * @param mappedLayers
  237. * @returns query filters
  238. */
  239. createLocalFilters: function createLocalFilters(queryContext, mappedLayers) {
  240. var _this2 = this;
  241. var queryFilters = [];
  242. if (_.isEmpty(queryContext)) {
  243. return queryFilters;
  244. }
  245. var excludeNonProjectedRangeFilters = false;
  246. var categoryItemsForFirstMappedLayer = null;
  247. _.each(mappedLayers, function (mappedLayer) {
  248. //NonProjected range filters cannot be applied to any layer whose categories are not the same as the first mapped layer
  249. //(because the aggregate of different categories is different.)
  250. var categoryItemsForThisLayer = mappedLayers.length > 1 && _this2._getCategoryItemsForLayer(mappedLayer.id) || [];
  251. categoryItemsForFirstMappedLayer = categoryItemsForFirstMappedLayer || categoryItemsForThisLayer;
  252. excludeNonProjectedRangeFilters = _this2._categoriesAreDifferent(categoryItemsForFirstMappedLayer, categoryItemsForThisLayer);
  253. var filtersConvertedToItemSelections = [];
  254. _.each(queryContext.filtersConvertedToDataItemSelections, function (excludedFilterEntry) {
  255. filtersConvertedToItemSelections.push(excludedFilterEntry.getId());
  256. });
  257. var queryFilterSpec = _this2._createQueryFilterSpec( /*projected data items*/queryContext.projectedDataItemsMap);
  258. if (queryContext.entities) {
  259. queryFilterSpec.addFiltersToSpec(queryContext.entities.localFilters, {
  260. edgeFilterExceptions: filtersConvertedToItemSelections,
  261. layerId: mappedLayer.getId(),
  262. excludeNonProjectedRangeFilters: excludeNonProjectedRangeFilters,
  263. shouldApplyIsNullValueExpression: _this2._shouldApplyIsNullValueExpression.bind(_this2),
  264. addExtraQueryDataItem: queryContext && queryContext.queryOptions && queryContext.queryOptions.addExtraQueryDataItem
  265. });
  266. queryFilterSpec.addFiltersToSpec(queryContext.entities.searchFilters, {
  267. edgeFilterExceptions: filtersConvertedToItemSelections
  268. });
  269. var layerFilters = queryFilterSpec.hasFilterSpec() ? queryFilterSpec.getFilterSpec() : null;
  270. if (layerFilters) {
  271. _.each(layerFilters, function (layerFilter) {
  272. layerFilter.layerId = mappedLayer.getId();
  273. queryFilters.push(layerFilter);
  274. });
  275. }
  276. }
  277. });
  278. if (queryContext.queryOptions && queryContext.queryOptions.extraFilters) {
  279. // list of filters to be added to the generated filters list
  280. queryFilters = queryFilters.concat(queryContext.queryOptions.extraFilters);
  281. }
  282. return queryFilters;
  283. },
  284. /**
  285. * @returns true if need to combine isnull in exclude filter (in FilterEntry._buildEdgeNullValueExpresstionQuerySpec)
  286. * 'filters': [
  287. {
  288. 'type': 'pre',
  289. 'expression': {
  290. 'or': [{
  291. 'itemId': 'someItem',
  292. 'operator': 'isnull'
  293. },
  294. {
  295. 'operator': 'notin',
  296. 'itemId': 'someItem',
  297. 'values': [
  298. 'XXX'
  299. ]
  300. }
  301. ]
  302. }
  303. }]
  304. *
  305. * The function should return false for an OLAP column
  306. */
  307. _shouldApplyIsNullValueExpression: function _shouldApplyIsNullValueExpression(filterEntry) {
  308. if (!filterEntry.columnId) {
  309. return false;
  310. }
  311. var column = this.visAPI.getMetadataColumn(filterEntry.columnId);
  312. return column && !column.isOlapColumn();
  313. },
  314. _canQuery: function _canQuery(slotAPI) {
  315. return !(slotAPI.isMultiMeasuresSeries() && !slotAPI.isStacked());
  316. },
  317. _getQuerySlots: function _getQuerySlots() {
  318. return _.filter(this.visAPI.getDataSlots(), function (slotAPI) {
  319. return this._canQuery(slotAPI);
  320. }.bind(this));
  321. },
  322. _getQueryLayers: function _getQueryLayers() {
  323. return this.visAPI.getLayers();
  324. },
  325. //TODO: Remove the function and sub functions when 'fields' is not needed in the flow for Endor
  326. _slotToFields: function _slotToFields(slotAPI) {
  327. //, bDoNotInverseSort) {
  328. var fields = [];
  329. _.each(slotAPI.getDataItemAPIs(), function (dataItemAPI, index) {
  330. if (!slotAPI.isMultiMeasuresSeriesOrValue(index)) {
  331. fields.push(this._slotItemToField(slotAPI, dataItemAPI));
  332. }
  333. }.bind(this));
  334. return fields;
  335. },
  336. /**
  337. * @private
  338. * @return {Array} An array of projection IDs of all items assigned to a single slot.
  339. **/
  340. _slotToProjections: function _slotToProjections(slotAPI, queryContext) {
  341. var projections = [];
  342. // filterProjections will be a function only for summary queries
  343. var filterProjections = queryContext && queryContext.queryOptions && queryContext.queryOptions.filterProjections;
  344. var isSummary = typeof filterProjections === 'function';
  345. var validDataItemIds = _.filter(slotAPI.getDataItemRefs(), function (id, index) {
  346. return !slotAPI.isMultiMeasuresSeriesOrValue(index);
  347. });
  348. // If the slot is stacked and if this is not a summary query, produce a single projection for the slot (using the slot id)
  349. if (slotAPI.isStacked() && validDataItemIds.length > 1 && !isSummary) {
  350. projections.push({
  351. id: slotAPI.getId(),
  352. layerId: slotAPI.getLayerId()
  353. });
  354. } else {
  355. _.each(validDataItemIds, function (dataItemId) {
  356. projections.push({
  357. id: dataItemId,
  358. layerId: slotAPI.getLayerId()
  359. });
  360. });
  361. }
  362. return projections;
  363. },
  364. /**
  365. * @private
  366. * @param slotAPI - the slotAPI of interest
  367. * @param queryContext - the context of the query.
  368. * @return {Array} An array of Data Item objects assigned to a single slot.
  369. **/
  370. _slotToDataItems: function _slotToDataItems(slotAPI, queryContext) {
  371. var aDataItems = [];
  372. var nestList = [];
  373. _.each(slotAPI.getDataItemAPIs(), function (dataItemAPI, index) {
  374. if (!slotAPI.isMultiMeasuresSeriesOrValue(index)) {
  375. aDataItems.push(this.visQueryDataItemBuilder.build(slotAPI, dataItemAPI, queryContext, index));
  376. //keep track of the list of dataItems assigned to a slot.
  377. //If a slot has multiple items and the slot is type stacked,
  378. //use this list as the nestList of a stacke
  379. nestList.push(dataItemAPI.getUniqueId());
  380. }
  381. }.bind(this));
  382. if (nestList.length > 1 && slotAPI.isStacked()) {
  383. //If the slot is stacked, produce a dataItem whose name matches the id
  384. //with nested items whose names match the dataItems.
  385. aDataItems.push({
  386. id: slotAPI.getId(),
  387. nest: nestList,
  388. layerId: slotAPI.getLayerId()
  389. });
  390. }
  391. return aDataItems;
  392. },
  393. /**
  394. * @returns the field equivalent of this dataslot.
  395. */
  396. //TODO: Remove the function and sub functions when 'fields' is not needed in the flow for Endor
  397. _slotItemToField: function _slotItemToField(slotAPI, dataItemAPI) {
  398. return {
  399. id: slotAPI.getId(),
  400. columnId: dataItemAPI.getItemId(),
  401. column: {
  402. isDateOrTimeType: function isDateOrTimeType() {
  403. return dataItemAPI.isDateOrTimeType();
  404. },
  405. getFormat: function getFormat() {
  406. return null; //Needed for summary view rendering.
  407. }
  408. },
  409. //sort: sortOrder,
  410. //limit: mapping ? mapping.limit : undefined,
  411. //sourceCategory: mapping ? mapping.getSourceCategory() : undefined,
  412. //filterException: this.getFilterException(),
  413. aggType: dataItemAPI.getAggregationType(),
  414. getType: function () {
  415. return this._getSlotType(slotAPI, dataItemAPI);
  416. }.bind(this),
  417. label: dataItemAPI.getLabel(),
  418. dataType: dataItemAPI.getDataType(),
  419. layerId: slotAPI.getLayerId()
  420. //useInTopBottomQueries: this.shouldBeUsedInTopBottomQueries(),
  421. //categorySubType: slot.getCategorySubType(),
  422. //ignoreSlotForViz: this.definition.ignoreSlotForViz,
  423. //postSortResultsDescending: this.postSortResultsDescending()
  424. };
  425. },
  426. /**
  427. * Returns the type of this slot as one of category, ordinal or 'any'.
  428. * Type 'any' will only be returned for unmapped slots
  429. * @returns the slot type. For slots of type 'any', the mapping will be used to determine the type.
  430. */
  431. _getSlotType: function _getSlotType(slotAPI, dataItemAPI) {
  432. if (slotAPI.getType() === 'any') {
  433. return dataItemAPI.getType() === 'attribute' ? 'category' : dataItemAPI.getType() === 'fact' ? 'ordinal' : 'category';
  434. }
  435. return slotAPI.getType();
  436. }
  437. });
  438. return VisQueryBuilder;
  439. });
  440. //# sourceMappingURL=VisQueryBuilder.js.map