VisQueryExecution.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: Dashboard
  6. *| (C) Copyright IBM Corp. 2013, 2020
  7. *|
  8. *| US Government Users Restricted Rights - Use, duplication or disclosure
  9. *| restricted by GSA ADP Schedule Contract with IBM Corp.
  10. *+------------------------------------------------------------------------+
  11. */
  12. // The VisQueryExecution object manages a set of queries that are needed to provide the data for a visualization.
  13. define(['jquery', 'underscore', './VisQueryBuilder', '../../../lib/@waca/core-client/js/core-client/ui/core/Class', './CommonQueryBuilder', './legacy/QueryManager', '../../../widgets/livewidget/nls/StringResources', '../../../dataSources/utils/DatasourceUtil', './postprocess/VisQueryPostProcessHelper'], function ($, _, VisQueryBuilder, Class, CommonQueryBuilder, QueryManager, StringResources, DatasourceUtil, PostProcessHelper) {
  14. 'use strict';
  15. var VisQueryExecution = null; // class declaration
  16. VisQueryExecution = Class.extend({
  17. init: function init(options) {
  18. console.debug('visQueryExecution (lw) init');
  19. VisQueryExecution.inherited('init', this, arguments);
  20. options = options || {};
  21. this.queryService = options.queryService;
  22. this.ownerWidget = options.ownerWidget;
  23. this.metadataAPI = options.metadataAPI;
  24. this.pageContext = options.pageContext;
  25. this.visAPI = options.visAPI;
  26. this._commonQueryHelper = options.queryHelper;
  27. //Optionally, override this function to perform operations on the data prior to post processing.
  28. this._callUnprocessedResultDataHandlers = options.callUnprocessedResultDataHandlers || this._callUnprocessedResultDataHandlers;
  29. this._queryManager = new QueryManager({
  30. 'queryService': this.queryService,
  31. 'visAPI': this.visAPI,
  32. 'dashboardAPI': this.ownerWidget && this.ownerWidget.getDashboardApi && this.ownerWidget.getDashboardApi(),
  33. 'postProcessCallback': this.postProcess.bind(this),
  34. '_callUnprocessedResultDataHandlers': this._callUnprocessedResultDataHandlers.bind(this)
  35. });
  36. if (!this.visQueryBuilder) {
  37. var cbGetNLT = this.ownerWidget && this.ownerWidget.getFeature && this.ownerWidget.getFeature.bind(this.ownerWidget, 'vis-chart-insights');
  38. this.visQueryBuilder = new VisQueryBuilder({
  39. 'visAPI': this.visAPI,
  40. 'getNLT': cbGetNLT
  41. });
  42. }
  43. //The _queryChangedCompareString is used to avoid querying in cases where a spec change has no impact on the query definition (or result)
  44. this._queryChangedCompareString = null;
  45. },
  46. /**
  47. * Ensure that the slots are up-to-date and prepare the data (if necessary).
  48. * @param localFiltersOnly (optional) if set, only local filters will be passed to the queryDefinition. If not set, both local and cross-widget filters are passed.
  49. * @returns a promise.
  50. */
  51. execute: function execute(queryContext) {
  52. if (queryContext.isInvalidReason) {
  53. queryContext.cbQueryFailed({
  54. reason: queryContext.isInvalidReason
  55. });
  56. return Promise.reject();
  57. }
  58. return this.getQueryOptionsArray(queryContext).then(function (result) {
  59. result = result || {};
  60. // Compare with the main data query.
  61. // Auxiliary queries (ie. summaries) can be ignored.
  62. var aQueryOptionsArray = result.aQueryOptionsArray;
  63. if (!queryContext.auxQuery) {
  64. this._queryChangedCompareString = this._buildQueryChangedCompareString(aQueryOptionsArray);
  65. }
  66. return this._doExecute(queryContext, aQueryOptionsArray).then(function (resultData) {
  67. if (resultData) {
  68. resultData.synchDataFilterEntries = result.synchDataFilterEntries;
  69. }
  70. return resultData;
  71. });
  72. }.bind(this));
  73. },
  74. //This function can be overridden in constructor options to perform tasks on un-postprocessed data.
  75. _callUnprocessedResultDataHandlers: function _callUnprocessedResultDataHandlers() {},
  76. postProcess: function postProcess(resultDataEntry) {
  77. PostProcessHelper.postProcessMeasuresAsSeries(resultDataEntry, this);
  78. PostProcessHelper.postProcessAggregatedSort(resultDataEntry, this);
  79. PostProcessHelper.postProcessCustomSort(resultDataEntry, this);
  80. PostProcessHelper.postProcessAutobinningAxisLabels(resultDataEntry, this);
  81. },
  82. _doExecute: function _doExecute(queryContext, queryOptions) {
  83. var _this = this;
  84. return this.getQueryManager().whenQueryResultsReady(queryContext.renderContext, queryOptions, queryContext.sender, queryContext.useAPI, queryContext.auxQuery).catch(function (jqXHR) {
  85. return Promise.reject(_this._queryFailed(jqXHR));
  86. });
  87. },
  88. getQueryOptionsArray: function getQueryOptionsArray(queryContext) {
  89. //lat/long TODO, This is array of query definition
  90. //Lat/Long new query Definition Object
  91. return this._createQueryDefinition(queryContext).then(function (result) {
  92. result = result || {};
  93. var visQueryDefinition = result.visQueryDefinition;
  94. var aQueryOptionsArray = [];
  95. var oSpecsForQueryDefinition = visQueryDefinition.getQueryDefinitions(); // hand back
  96. _.each(oSpecsForQueryDefinition, function (oDef) {
  97. var oQueryDefObj = this.getQueryDefinition();
  98. oQueryDefObj.setQueryDataItems(oDef.getDataItems());
  99. oQueryDefObj.setQueryProjections(oDef.getProjections());
  100. //TopBottom queries need to be 'scoped' to items on the current page (tab)
  101. oQueryDefObj.setTopBottomQueryPageScope(queryContext.scope);
  102. var initialQueryOptions = queryContext.queryOptions ? _.clone(queryContext.queryOptions) : {};
  103. var queryOptions = _.extend(initialQueryOptions, oQueryDefObj.build(initialQueryOptions, this.ownerWidget.visAPI));
  104. // Set filters to query spec
  105. var filters = oDef.getFilters();
  106. if (filters && filters.length) {
  107. queryOptions.querySpec.filters = filters;
  108. }
  109. queryOptions.sourceIdOrModule = this.metadataAPI.getModule();
  110. if (queryOptions.querySpec.queryHints) {
  111. _.extend(queryOptions.querySpec.queryHints, oDef.getQueryHints());
  112. } else {
  113. queryOptions.querySpec.queryHints = oDef.getQueryHints();
  114. }
  115. if (queryContext.queryOptions && queryContext.queryOptions.needsUnaggregatedForm) {
  116. queryOptions.querySpec.type = 'detail';
  117. }
  118. // Pass the functions to handle prompt fault.
  119. queryOptions.promptControlFunctions = {
  120. 'preparePromptSpec': this.preparePromptSpec.bind(this),
  121. 'whenSingleItemQueryReady': this._commonQueryHelper.whenSingleItemQueryReady.bind(this._commonQueryHelper, queryOptions.sourceIdOrModule),
  122. 'whenColumnsMinMaxQueryReady': this._commonQueryHelper.whenColumnsMinMaxQueryReady.bind(this._commonQueryHelper, queryOptions.sourceIdOrModule),
  123. 'getPromptSpec': this.getPromptSpec.bind(this),
  124. 'savePromptSpec': this.savePromptSpec.bind(this),
  125. 'updatePromptSpecCache': this.updatePromptSpecCache.bind(this),
  126. 'onCancelPromptDialog': this.onCancelPromptDialog.bind(this),
  127. 'onUnSupportedPromptType': this.ownerWidget.onUnSupportedPrompt.bind(this, StringResources.get('unSupportedPromptType'))
  128. };
  129. queryOptions.layerId = oDef.getLayerId();
  130. if (this.ownerWidget && this.ownerWidget.isPreview) {
  131. queryOptions.isPreview = true;
  132. }
  133. aQueryOptionsArray.push(queryOptions);
  134. }, this);
  135. return { aQueryOptionsArray: aQueryOptionsArray, synchDataFilterEntries: result.synchDataFilterEntries };
  136. }.bind(this));
  137. },
  138. _buildQueryChangedCompareString: function _buildQueryChangedCompareString(results) {
  139. var queryChangedCompareString = '';
  140. _.each(results, function (result) {
  141. queryChangedCompareString += JSON.stringify(result.querySpec);
  142. });
  143. return queryChangedCompareString;
  144. },
  145. queryChanged: function queryChanged(queryContext) {
  146. return this.getQueryOptionsArray(queryContext).then(function (result) {
  147. var queryUrl = this._buildQueryChangedCompareString(result.aQueryOptionsArray);
  148. return {
  149. isRenderNeeded: this._queryChangedCompareString !== queryUrl,
  150. synchDataFilterEntries: result.synchDataFilterEntries
  151. };
  152. }.bind(this));
  153. },
  154. _createQueryDefinition: function _createQueryDefinition() {
  155. var _this2 = this;
  156. var queryContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  157. queryContext.queryOptions = queryContext.queryOptions || {};
  158. queryContext.queryOptions.addExtraQueryDataItem = CommonQueryBuilder.addExtraQueryDataItem.bind(this, this.ownerWidget.model.getDataItemIdsMap(), queryContext.queryOptions);
  159. var visQueryDefinition = this.visQueryBuilder.create(queryContext);
  160. queryContext.metadataAPI = this.metadataAPI;
  161. var netPageContext = this._getNetPageContext(this.ownerWidget.visAPI.getDefinition().reactToExternalBrushing);
  162. var binningInfos = [];
  163. var aFilters = CommonQueryBuilder.buildFilterFromPageContext(netPageContext, queryContext, binningInfos);
  164. var extraQueryDataItems = queryContext.queryOptions.extraDataItems && queryContext.queryOptions.extraDataItems.length ? queryContext.queryOptions.extraDataItems : null;
  165. _.each(visQueryDefinition.getQueryDefinitions(), function (queryDefinition) {
  166. _this2._addBinningDataItemSpecIfNeedit(queryDefinition._dataItems, binningInfos);
  167. // Add extra query data items required by filter expression, if not already added
  168. if (extraQueryDataItems) {
  169. queryDefinition._dataItems = _.uniq(queryDefinition._dataItems.concat(extraQueryDataItems), false, function (dataItem) {
  170. return dataItem.id;
  171. });
  172. }
  173. queryDefinition.addFilters(aFilters);
  174. });
  175. return Promise.resolve({ visQueryDefinition: visQueryDefinition, synchDataFilterEntries: null });
  176. },
  177. _addBinningDataItemSpecIfNeedit: function _addBinningDataItemSpecIfNeedit(dataItems, binningInfos) {
  178. if (binningInfos) {
  179. CommonQueryBuilder.addBinningDataItemIfNeedit(dataItems, binningInfos);
  180. }
  181. },
  182. _isDifferentModule: function _isDifferentModule(queryContext) {
  183. queryContext = queryContext || {};
  184. if (!queryContext.sourceIds && !queryContext.assetIds) {
  185. return false;
  186. } else {
  187. return this.ownerWidget.idsNotInModule(queryContext.sourceIds, queryContext.assetIds);
  188. }
  189. },
  190. _getTableRef: function _getTableRef() {
  191. var tableRef = null;
  192. var visualization = this.ownerWidget.content.getFeature('Visualization');
  193. var datasource = visualization.getDataSource();
  194. if (datasource) {
  195. var slots = visualization.getSlots();
  196. var dataItems = slots.getDataItemList();
  197. tableRef = DatasourceUtil.getTableRef(datasource, dataItems.map(function (dataItem) {
  198. return dataItem.getColumnId();
  199. }));
  200. }
  201. return tableRef;
  202. },
  203. _getNetPageContext: function _getNetPageContext(reactToExternalBrushing) {
  204. var _this3 = this;
  205. var param = {
  206. scope: this.ownerWidget.getContainerPageId(),
  207. eventGroupId: this.ownerWidget.getEventGroupId(),
  208. sourceId: this.metadataAPI.getModule().getSourceId()
  209. };
  210. if (reactToExternalBrushing === false) {
  211. param.origin = 'filter';
  212. }
  213. var netPageContext = this.pageContext && this.pageContext.getNetPageContext(param) || [];
  214. // Longer term - this filtering should be removed. The query service will ignore filters that don't apply
  215. // (instead of throwing an error) and return messages indicating that certain filters are being ignored.
  216. // Shorter term - we may need to compare table references to avoid errors but if there is no table ref,
  217. // just let it pass through (e.g. for a global calculation) and let the query service handle it.
  218. var tableRef = this._getTableRef();
  219. var isEmptyTableRef = function isEmptyTableRef(aTableRef) {
  220. return !aTableRef || _.every(aTableRef, function (ref) {
  221. return _.isEmpty(ref);
  222. });
  223. };
  224. var haveTableJoin = function haveTableJoin(pageContext) {
  225. if (isEmptyTableRef(tableRef)) {
  226. return true;
  227. }
  228. var pageContextTableRef = DatasourceUtil.getPageContextItemTableRef(_this3.metadataAPI.getModule(), pageContext);
  229. return isEmptyTableRef(pageContextTableRef) || DatasourceUtil.haveTableJoinsInSameDataSource(_this3.metadataAPI.getModule(), pageContextTableRef, tableRef);
  230. };
  231. netPageContext = netPageContext.filter(function (pageContext) {
  232. // Don't send anything page context entries that tables are no joined, otherwise get error from such as:
  233. // "code": "XQE-MSR-0008",
  234. // "details": "XQE-MSR-0008 In module \"newModel\", the following query subjects are not joined: \"Page_1\", \"Page_2\", \"Synch Source 2.xlsx\".\r\n\t
  235. // TODO - livewidget_cleanup -- this code used the old slot API -- verifdy if this is this dead code ?
  236. return DatasourceUtil.mustVerifyJoinTablesInSameDataSource(_this3.metadataAPI.getModule(), pageContext, _this3.visAPI.getDataSlots()) ? haveTableJoin(pageContext) : true;
  237. });
  238. return netPageContext;
  239. },
  240. _getNetPageContextFromOtherModules: function _getNetPageContextFromOtherModules() {
  241. var _this4 = this;
  242. var netPageContext = [];
  243. if (this.pageContext) {
  244. var currentSourceId = this.metadataAPI.getModule().getSourceId();
  245. var tableRef = this._getTableRef();
  246. var options = {
  247. scope: this.ownerWidget.getContainerPageId(),
  248. eventGroupId: this.ownerWidget.getEventGroupId(),
  249. origin: 'filter'
  250. };
  251. var pageContext = this.pageContext.getNetPageContextItems(options);
  252. netPageContext = pageContext.filter(function (context) {
  253. var otherTableRef = DatasourceUtil.getPageContextItemTableRef(_this4.metadataAPI.getModule(), context.getPageContextSpec());
  254. return context.getSourceId() !== currentSourceId || !DatasourceUtil.haveTableJoinsInSameDataSource(_this4.metadataAPI.getModule(), otherTableRef, tableRef);
  255. });
  256. }
  257. return netPageContext;
  258. },
  259. _queryFailed: function _queryFailed(failure) {
  260. var knownFailures = ['unSupportedPromptType', 'cancelPromptDialog', 'promptingIsDisabled'];
  261. if (knownFailures.indexOf(failure.message) !== -1) {
  262. // this failure will be handled in Explore
  263. return failure;
  264. }
  265. var oDS = this.metadataAPI.getModule();
  266. var msg = 'dwErrorRunningQuery';
  267. var code = void 0;
  268. if (failure.reason === 'staleRequest') {
  269. msg = 'dwErrorStaleRequest';
  270. } else if (failure.reason === 'geoQueryFail') {
  271. msg = 'dwErrorGeoData';
  272. } else if (failure.reason === 'cancelPromptSignon') {
  273. msg = 'dwPromptSignonCancelWarning';
  274. } else if (failure.responseJSON && failure.responseJSON.errors) {
  275. var oErr = failure.responseJSON.errors[0];
  276. code = oErr && oErr.code;
  277. if (code === 'DSS-GEN-0002') {
  278. this._hasUnavailableMetadataColumns = true;
  279. //M31 FIXME?
  280. //this.trigger('hasUnavailableMetadataColumns');
  281. return;
  282. } else if (code === 'DSS-GEN-0001') {
  283. msg = StringResources.get('errorSourceNotFound', {
  284. sourceName: oDS.getSourceName() || oDS.getSourceId()
  285. });
  286. } else if (code === 'XQE-PLN-0226') {
  287. msg = StringResources.get('errorActionNotSupported', {
  288. sourceName: oDS.getSourceName() || oDS.getSourceId()
  289. });
  290. } else if (oErr && oErr.message) {
  291. msg = oErr.message;
  292. }
  293. }
  294. var oParam = {};
  295. if (oDS) {
  296. oParam = {
  297. 'datasetName': oDS.getSourceName() || oDS.getSourceId()
  298. };
  299. }
  300. if (failure.reason !== 'staleRequest') {
  301. this.visAPI.setInvalidReason({
  302. msg: msg,
  303. code: code
  304. });
  305. }
  306. return _.extend(new Error(), {
  307. msg: msg,
  308. param: oParam,
  309. hasUnavailableMetadataColumns: this._hasUnavailableMetadataColumns,
  310. errorInfo: failure
  311. });
  312. },
  313. getQueryService: function getQueryService() {
  314. return this.queryService;
  315. },
  316. /**
  317. * @return the query manager object which owns the query definition and results of
  318. * this widget's query.
  319. */
  320. getQueryManager: function getQueryManager() {
  321. return this._queryManager;
  322. },
  323. /**
  324. * @returns the query definition object which defines fields for the query.
  325. */
  326. getQueryDefinition: function getQueryDefinition() {
  327. return this.getQueryManager().getQueryDefinition();
  328. },
  329. /**
  330. * @returns the query results object which owns field values from the last executed query
  331. */
  332. getQueryResults: function getQueryResults() {
  333. return this.getQueryManager().getQueryResults();
  334. },
  335. savePromptSpec: function savePromptSpec(queryOptions) {
  336. this.ownerWidget.savePromptSpec(queryOptions);
  337. },
  338. getPromptSpec: function getPromptSpec(promptName) {
  339. return this.ownerWidget.getPromptSpec(promptName);
  340. },
  341. onCancelPromptDialog: function onCancelPromptDialog() {
  342. this.ownerWidget.onCancelPromptDialog();
  343. },
  344. updatePromptSpecCache: function updatePromptSpecCache(queryOptions) {
  345. var currPromptSpecs = queryOptions && queryOptions.querySpec ? queryOptions.querySpec.parameterValues : null;
  346. // When the query is nativeQuery, i.e. internally generated queries, we should not update saved prompts.
  347. // If UI knows which prompt is referenced by which data item, nativeQuery can be removed.
  348. if (!this.ownerWidget || queryOptions && queryOptions.nativeQuery) {
  349. return;
  350. }
  351. var savedSpecs = this.getPromptSpec();
  352. var newSpecs;
  353. if (savedSpecs) {
  354. if (currPromptSpecs && currPromptSpecs.length > 0) {
  355. var promptNames = _.pluck(currPromptSpecs, 'name');
  356. newSpecs = _.pick(savedSpecs, promptNames);
  357. }
  358. this.ownerWidget.setPromptSpecs(newSpecs);
  359. }
  360. },
  361. // this is called by reprompt.
  362. getPromptQueries: function getPromptQueries() {
  363. var module = this.metadataAPI.getModule();
  364. return {
  365. 'whenSingleItemQueryReady': this._commonQueryHelper.whenSingleItemQueryReady.bind(this._commonQueryHelper, module),
  366. 'whenColumnsMinMaxQueryReady': this._commonQueryHelper.whenColumnsMinMaxQueryReady.bind(this._commonQueryHelper, module),
  367. 'savePromptSpec': this.savePromptSpec.bind(this),
  368. 'updatePromptSpecCache': this.updatePromptSpecCache.bind(this)
  369. };
  370. },
  371. // TODO This function should be moved to PromptManager once we support OLAP prompts
  372. // and get rid of OLAP metadata checking (querySpecInvolvesOlapColumn).
  373. preparePromptSpec: function preparePromptSpec(jqXHR) {
  374. var errors = jqXHR.responseJSON.errors;
  375. var error = errors.length > 0 ? errors[0] : null;
  376. if (!error || !error.code) {
  377. return;
  378. }
  379. var params = error.parameters;
  380. if (!params || params.length === 0) {
  381. return;
  382. }
  383. var code = error.code;
  384. var aParamInfo = [];
  385. switch (code) {
  386. case 'QF-888':
  387. if (this.querySpecInvolvesOlapColumn()) {
  388. aParamInfo.push({
  389. involvesOlapColumn: true
  390. });
  391. } else {
  392. _.each(params, function (param) {
  393. param.value.name = param.name;
  394. param.value.errorCode = code;
  395. // TODO this part should be removed once the prompt fault has the query item ID refering the prompt.
  396. param.value.isMultiPrompt = params.length > 1;
  397. if (param.value.capabilities.optional) {
  398. // Do not prompt for optional prompt and save it with nil value so xqe will ignore this prompt.
  399. var optionalPrompt = _.extend(param.value, { values: [{ 'mun': 'nil' }] });
  400. this.ownerWidget.savePromptSpec(optionalPrompt);
  401. } else {
  402. aParamInfo.push(param.value);
  403. }
  404. }.bind(this));
  405. }
  406. break;
  407. default:
  408. break;
  409. }
  410. return aParamInfo;
  411. },
  412. querySpecInvolvesOlapColumn: function querySpecInvolvesOlapColumn() {
  413. var hasOlapColumn = false;
  414. var dataItemAPIs = this.ownerWidget.content.getFeature('Visualization').getSlots().getDataItemList();
  415. if (dataItemAPIs && dataItemAPIs.length > 0) {
  416. for (var i = 0; i < dataItemAPIs.length; ++i) {
  417. if (this._isOlapColumn(dataItemAPIs[i].getColumnId())) {
  418. hasOlapColumn = true;
  419. break;
  420. }
  421. }
  422. }
  423. return hasOlapColumn;
  424. },
  425. _isOlapColumn: function _isOlapColumn(columnId) {
  426. var metadataColumn = this.metadataAPI.getMetadataColumn(columnId);
  427. return metadataColumn && typeof metadataColumn.isOlapColumn === 'function' ? metadataColumn.isOlapColumn() : false;
  428. }
  429. });
  430. return VisQueryExecution;
  431. });
  432. //# sourceMappingURL=VisQueryExecution.js.map