QueryManager.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: Dashboard
  5. * (C) Copyright IBM Corp. 2017, 2019
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. *
  8. */
  9. define(['./QueryDefinition', '../../../../lib/@waca/dashboard-common/dist/core/Model', '../../../../features/content/dataQueryExecution/DataQueryResult', '../QueryResultData', './QueryManagerGeoHelper', '../../../../lib/@waca/dashboard-common/dist/query/FacetDataObject', '../QueryResultObject', '../../../../features/content/dataQueryExecution/QueryResults', '../../../../features/content/dataQueryExecution/api/QueryResultsAPI', 'underscore'], function (QueryDefinition, Model, DataQueryResult, DeprecatedQueryResultData, Helper, FacetData, QueryResultObject, QueryResults, QueryResultsAPI, _) {
  10. 'use strict';
  11. /**
  12. * The QueryManager is responsible for hiding the details of querying for data,
  13. * filtering data and providing results to widgets.
  14. *
  15. * Widgets understand data as rows in 'slot order'.
  16. * The QueryManager does things like:
  17. * - Allowing widgets to form a query in a field order they specify (corresponding to slots and mappings)
  18. * to hide the fact that queries are made in 'dimension first' order
  19. * - returning the data back to the widget as rows in the same order (and as indexed columns or ranges)
  20. * - filtering, sorting.
  21. * - presenting a subset of rows
  22. */
  23. var QueryManager = Model.extend({
  24. init: function init(options) {
  25. QueryManager.inherited('init', this, arguments);
  26. this.dashboardAPI = options.dashboardAPI;
  27. this.logger = options.dashboardAPI && options.dashboardAPI.getGlassCoreSvc('.Logger');
  28. this.queryDefinition = new QueryDefinition({ dashboardAPI: this.dashboardAPI, logger: this.logger });
  29. //Latlong
  30. this.queryDefinitions = [];
  31. // the map used to cache refreshing queries
  32. this._refreshQueriesMap = {};
  33. },
  34. getVesion: function getVesion() {
  35. return 'endor';
  36. },
  37. clearCachedResponses: function clearCachedResponses() {
  38. this._topBottomQueryCache.clear();
  39. },
  40. /**
  41. * The following routine is used to send the needed queries as ajax calls
  42. * and return their promises in an array. We can wait for all the queries to
  43. * complete using this promises array.
  44. * @param {Array} queryOptionsArray object holding the querySpec and topBottomQuery
  45. * @param {String} sender (Optional) query sender
  46. * @return {Promise}
  47. */
  48. _sendQueriesAndGetPromises: function _sendQueriesAndGetPromises(queryOptionsArray, sender, useAPI) {
  49. var _this = this;
  50. var queryPromises = [];
  51. this.queryService.updateRequestId(sender);
  52. _.each(queryOptionsArray, function (queryOptions) {
  53. queryOptions.senderId = _this.visAPI.ownerWidget.id;
  54. queryOptions.sender = sender;
  55. var sourceIdOrModule = useAPI ? queryOptions.sourceIdOrModule.getSourceId() : queryOptions.sourceIdOrModule;
  56. var queryService = useAPI ? _this.dashboardAPI.getFeature('QueryService') : _this.queryService;
  57. // Run the main query Spec (if one is defined)
  58. if (queryOptions.querySpec) {
  59. var queryPromise = useAPI ? queryService.executeQuery(sourceIdOrModule, queryOptions.querySpec, sender) : queryService.runQuery(queryOptions);
  60. queryPromises.push({ layerId: queryOptions.layerId, promise: queryPromise });
  61. }
  62. // Run the top and bottom query if it exists
  63. if (queryOptions.topBottomQuery) {
  64. var topbottomQueryPromise = useAPI ? queryService.executeQuery(sourceIdOrModule, queryOptions.topBottomQuery, sender) : _this.queryService.runQuery({
  65. querySpec: queryOptions.topBottomQuery,
  66. sourceIdOrModule: queryOptions.sourceIdOrModule
  67. });
  68. queryPromises.push({
  69. layerId: queryOptions.layerId,
  70. promise: topbottomQueryPromise,
  71. type: 'topbottom'
  72. });
  73. }
  74. });
  75. return queryPromises;
  76. },
  77. _handleSuccessfulCompletionOfQueries: function _handleSuccessfulCompletionOfQueries(renderContext, queryPromiseResults, queryOptions, oriRequests) {
  78. var _this2 = this;
  79. var mainQueryResults;
  80. var completedTopBottomQuery;
  81. var promiseResults = _.toArray(queryPromiseResults);
  82. var oQueryResultObject = new QueryResultObject(renderContext);
  83. var sLayerId;
  84. _.each(promiseResults, function (result, index) {
  85. var bIsMainQuery = false;
  86. sLayerId = oriRequests[index].layerId;
  87. if (!oriRequests[index].type) {
  88. bIsMainQuery = true;
  89. mainQueryResults = result.data;
  90. }
  91. //We assume currently one layer has only one query request
  92. if (oriRequests[index + 1] && oriRequests[index + 1].layerId === sLayerId && oriRequests[index + 1].type === 'topbottom') {
  93. completedTopBottomQuery = promiseResults[index + 1].data;
  94. }
  95. var oQueryOption = _.find(queryOptions, function (entry) {
  96. return entry.layerId === sLayerId;
  97. });
  98. var mainQueryThreshold = oQueryOption.querySpec.limit;
  99. var topBottomByAggregateId = {};
  100. if (oQueryOption.topBottomQuery) {
  101. _this2._updateTopBottomByAggregateId(topBottomByAggregateId, completedTopBottomQuery);
  102. }
  103. if (bIsMainQuery) {
  104. // Should be able to just pass mapped data items, but due to DSS defect 274285, temporarily pass slots to build result.
  105. var slots = _this2.visAPI.ownerWidget.getAPI().getFeature('Visualization').getSlots();
  106. var deprecatedQueryResult = new DeprecatedQueryResultData(mainQueryResults);
  107. var queryResult = new DataQueryResult(mainQueryResults, slots);
  108. // Make the query spec and raw response available as properties in the query result data object
  109. if (queryOptions[index] && queryOptions[index].querySpec) {
  110. queryResult.setPropertyValue('QuerySpec.internal', JSON.stringify(queryOptions[index].querySpec));
  111. }
  112. // Ideally we should access the raw data from the ajax response as raw string,
  113. // but currentlt we don't have access to it because we query using modelling which we should mve away from
  114. queryResult.setPropertyValue('RawData.internal', JSON.stringify(mainQueryResults));
  115. var queryResultAPI = queryResult.getAPI();
  116. _this2._callUnprocessedResultDataHandlers && _this2._callUnprocessedResultDataHandlers(mainQueryResults, { layerId: sLayerId }, queryResultAPI);
  117. _this2.postProcessCallback(mainQueryResults);
  118. oQueryResultObject.addQueryResultObject(sLayerId, deprecatedQueryResult, mainQueryThreshold, {
  119. topBottomMappings: topBottomByAggregateId
  120. }, queryResultAPI);
  121. }
  122. });
  123. return oQueryResultObject;
  124. },
  125. _handleSucceededQueries: function _handleSucceededQueries(queryPromiseResults, oriRequests) {
  126. var queryResults = new QueryResults();
  127. var type = void 0;
  128. _.each(_.toArray(queryPromiseResults), function (result, index) {
  129. if (!oriRequests[index].type) {
  130. type = QueryResultsAPI.QUERY_RESULT_TYPE.MAIN;
  131. } else if (oriRequests[index].type === QueryResultsAPI.QUERY_RESULT_TYPE.TOPBOTTOM) {
  132. type = QueryResultsAPI.QUERY_RESULT_TYPE.TOPBOTTOM;
  133. }
  134. queryResults.addResult(result, oriRequests[index].layerId, type);
  135. });
  136. return queryResults.getAPI();
  137. },
  138. /**
  139. * Update a map of top bottom results mapped by their 'aggregateId'.
  140. * An aggregate id is a key which is the minimum needed to uniquely identify an aggregate (ie: its columnId + aggregate type).
  141. * eg:{ 3sum: { topResult: 100, bottomResult: -500 },
  142. * 7avg: { topResult: 10000, bottomResult: 600 },
  143. * 7sum: { topResult: 100000, bottomResult: 60 }}
  144. *
  145. * @param topBottomByAggregateId - The object to be updated which maps one or more AggregateIds to the top and bottom result rows.
  146. * @param completedTopBottomQuery - a query object that includes the descriptor of what was executed and the query response.
  147. * (as returned from queryCache.getQueryPromise() when it resolves).
  148. * @returns the updated topBottomByAgggregateId map
  149. */
  150. _updateTopBottomByAggregateId: function _updateTopBottomByAggregateId(topBottomByAggregateId, completedTopBottomQuery) {
  151. if (!completedTopBottomQuery || !completedTopBottomQuery.dataItems || completedTopBottomQuery.dataItems.length < 1) {
  152. return topBottomByAggregateId;
  153. }
  154. //min and max are stored in result pairs 2 per corresponding field (eg: result for field1, field2 is min1 max1 min2 max2)
  155. var data = completedTopBottomQuery.data[0].pt;
  156. var dataItems = completedTopBottomQuery.dataItems;
  157. var numDataItems = dataItems.length;
  158. for (var dataItemIndex = 0; dataItemIndex < numDataItems; dataItemIndex += 2) {
  159. // Need the min, max and aggregate type for the data item.
  160. var dataItemMin = dataItems[dataItemIndex];
  161. var dataItemAggType = dataItemMin.itemClass.h[0].aggregate;
  162. var areDataItemsAggregates = !!dataItemAggType; // Only do top and bottom for aggregates
  163. if (areDataItemsAggregates) {
  164. var dataItemMinValue = data[dataItemIndex].v; // First data item is the min
  165. var dataItemMaxValue = data[dataItemIndex + 1].v; // Second data item is the max
  166. var facetDataItemMin = new FacetData({ u: dataItemMinValue }); // Create Min and Max facet data items for legacy purposes.
  167. var facetDataItemMax = new FacetData({ u: dataItemMaxValue }); // TODO This can be cleaned up further when the legacy query code is updated.
  168. var columnName = dataItemMin.itemClass.h[0].u.split('_MIN')[0];
  169. this._updateTopBottom(topBottomByAggregateId, columnName + dataItemAggType, facetDataItemMin, facetDataItemMax);
  170. }
  171. }
  172. return topBottomByAggregateId;
  173. },
  174. /**
  175. * If the passed in arguments for bottom/top are less/greater than the current top/bottom values for this aggregate, widen the range.
  176. *
  177. * @param topBottomByAggregateId a map of aggregates keyed by their id (which is columnId + aggregation type).
  178. * @param aggregateId - the aggregateId to update the top and bottom for.
  179. * @param bottom - the bottom value to process
  180. * @param top - the top value to process
  181. */
  182. _updateTopBottom: function _updateTopBottom(topBottomByAggregateId, aggregateId, bottom, top) {
  183. if (!topBottomByAggregateId[aggregateId]) {
  184. topBottomByAggregateId[aggregateId] = {
  185. bottomResult: bottom,
  186. topResult: top
  187. };
  188. return;
  189. }
  190. if (bottom.useValue < topBottomByAggregateId[aggregateId].bottomResult.useValue) {
  191. topBottomByAggregateId[aggregateId].bottomResult = bottom;
  192. }
  193. if (top.useValue > topBottomByAggregateId[aggregateId].topResult.useValue) {
  194. topBottomByAggregateId[aggregateId].topResult = top;
  195. }
  196. },
  197. /**
  198. * Run the query and, when ready, process the queryResponses
  199. * queryResponses is an array of one or more query responses. The format is different for 'main queries' and 'minmax' queries..
  200. * [
  201. * //index 1: main query in queryResponses array is the result
  202. * {results: [['data', 'row' 1],['data', 'row' 2]], hasNext: false, hasPrev: false },
  203. * //index 2, 3 form: minmax query.
  204. * {queryDescriptor: {}, response: [['min1', 'max1', 'min2', 'max2'...]] },
  205. * {minmaxresponse2}
  206. * ]
  207. * @param renderContext - information about the current render (including the deferred to be resolved when the query is complete)
  208. * @param queryOptionsArray - any options to be put on the query (such as whether geoJson should be processed, a rowLimit should be imposed etc).
  209. * @param querySender (optional) - unique string identifier of the query sender (defaults to 'QueryManager')
  210. * @param isAuxQuery true if is an auxiliary query
  211. * @returns a promise.
  212. */
  213. whenQueryResultsReady: function whenQueryResultsReady() {
  214. var renderContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  215. var queryOptionsArray = arguments[1];
  216. var querySender = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'QueryManager';
  217. var _this3 = this;
  218. var useAPI = arguments[3];
  219. var isAuxQuery = arguments[4];
  220. var aOrigPromises = renderContext.noDataQuery ? this._getEmptyData(queryOptionsArray) : this._sendQueriesAndGetPromises(queryOptionsArray, querySender, useAPI);
  221. var queryPromises = _.pluck(aOrigPromises, 'promise');
  222. return Promise.all(queryPromises).then(function (results) {
  223. var ETags = [];
  224. _.each(results, function (result, index) {
  225. if (!aOrigPromises[index].type && !useAPI) {
  226. ETags.push(result.ETag); //Main Query
  227. }
  228. });
  229. // Check the last modified for the query data change.
  230. if (renderContext.extraInfo && renderContext.extraInfo.sender === 'realtimeRefresh') {
  231. // Use querySpec as the key to check if the query is already executed.
  232. var bAtLeastOneQueryChanged = false;
  233. _.each(queryOptionsArray, function (queryOptions, index) {
  234. var url = JSON.stringify(queryOptions.querySpec);
  235. if (!_this3._refreshQueriesMap[url] || _this3._refreshQueriesMap[url] !== ETags[index]) {
  236. bAtLeastOneQueryChanged = true;
  237. _this3._refreshQueriesMap[url] = ETags[index];
  238. }
  239. });
  240. if (!bAtLeastOneQueryChanged) {
  241. // Returns empty result when the query data did not change for real time refreshing.
  242. renderContext.sameQueryData = true;
  243. }
  244. }
  245. var result = void 0;
  246. if (!useAPI) {
  247. result = _this3._handleSuccessfulCompletionOfQueries(renderContext, results, queryOptionsArray, aOrigPromises);
  248. } else {
  249. result = _this3._handleSucceededQueries(results, aOrigPromises);
  250. }
  251. if (!isAuxQuery) {
  252. // Cache result if is not summary queries.
  253. _this3._queryResults = result;
  254. }
  255. return result;
  256. }).catch(function (jqXHR) {
  257. return Promise.reject(jqXHR);
  258. });
  259. },
  260. _getEmptyData: function _getEmptyData(queryOptions) {
  261. var _this4 = this;
  262. return _.map(queryOptions, function (option, index) {
  263. var dataItems = _this4.queryDefinitions[index].getQueryDataItems();
  264. return {
  265. layerId: option.layerId,
  266. promise: Promise.resolve({
  267. data: {
  268. dataItems: _.map(dataItems, function (dataItem) {
  269. var emptyDataItem = {
  270. itemClass: {
  271. h: [{
  272. u: dataItem.itemId,
  273. d: _this4.visAPI.getMetadataColumn(dataItem.itemId).getLabel()
  274. }],
  275. id: dataItem.id
  276. }
  277. };
  278. // ordinal dataitems requires an aggregation
  279. // while categorical dataitems requires an empty items array
  280. if (dataItem.aggregate) {
  281. emptyDataItem.itemClass.h[0].aggregate = dataItem.aggregate;
  282. } else {
  283. emptyDataItem.items = [];
  284. }
  285. return emptyDataItem;
  286. }),
  287. data: []
  288. }
  289. })
  290. };
  291. });
  292. },
  293. _shouldRejectGeoQueryFail: function _shouldRejectGeoQueryFail(geoResponse) {
  294. if (geoResponse && geoResponse.status === 'Fail') {
  295. if (!geoResponse.mapboxData) {
  296. return true;
  297. }
  298. if (geoResponse.mapboxData.length === 0) {
  299. return true;
  300. }
  301. return false;
  302. }
  303. return false;
  304. },
  305. /*
  306. * load the field values for the field in use
  307. */
  308. _getResultFieldValuesAtFieldIndex: function _getResultFieldValuesAtFieldIndex(queryResults, fieldIndex) {
  309. var values = [];
  310. var oDataItem;
  311. for (var i = 0; i < queryResults.getDataRows().length; i++) {
  312. oDataItem = queryResults.getDataItemByIndex(i, fieldIndex);
  313. values.push(oDataItem.displayValue);
  314. }
  315. return values;
  316. },
  317. getQueryResults: function getQueryResults() {
  318. return this._queryResults;
  319. },
  320. getQueryDefinition: function getQueryDefinition(layerId) {
  321. if (!layerId) {
  322. if (!this.queryDefinitions[0]) {
  323. this.queryDefinitions.push(new QueryDefinition({ dashboardAPI: this.dashboardAPI, logger: this.logger }));
  324. return this.queryDefinitions[0];
  325. }
  326. }
  327. var oResult = _.find(this.queryDefinitions, function (def) {
  328. return def.layerId === layerId;
  329. });
  330. if (!oResult) {
  331. oResult = this.queryDefinitions.push(new QueryDefinition({ layerId: layerId, dashboardAPI: this.dashboardAPI, logger: this.logger }));
  332. }
  333. return oResult;
  334. },
  335. getQueryDefinitions: function getQueryDefinitions() {
  336. return this.queryDefinitions;
  337. }
  338. });
  339. return QueryManager;
  340. });
  341. //# sourceMappingURL=QueryManager.js.map