waca_shaping.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: BI Dashboard
  6. *| (C) Copyright IBM Corp. 2017, 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. define(['underscore', '../../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../../lib/@waca/upgrades/UpgradeBase',
  13. // Ughn - this file should not be in dashboard-core... with dependencies on livewidget...
  14. 'dashboard-analytics/dataSources/models/DataSourcesModel', 'dashboard-analytics/dataSources/services/DataSourcesService'], function (_, Class, UpgradeBase, DataSourcesModel, DataSourcesService) {
  15. //NOSONAR
  16. var UpgradeShaping = Class.extend([UpgradeBase], {
  17. init: function init() {
  18. this.VERSION = 1005;
  19. },
  20. ORIGIN_VISUALIZATION: 'visualization',
  21. ORIGIN_FILTER: 'filter',
  22. /**
  23. * Perform upgrade
  24. *
  25. * @param {object} spec - spec to perform upgrade on
  26. *
  27. * @return {Promise} Promise to be resolved when upgrade performed
  28. */
  29. up: function up(spec) {
  30. this._calcIdMap = {};
  31. this.dataSourcesService = null;
  32. this._eventGroupMap = {};
  33. this._assetIdMap = {};
  34. this.spec = spec;
  35. this.dataSourcesService = new DataSourcesService({
  36. features: {
  37. API: this.data.dashboardApi,
  38. Logger: this.data.logger
  39. }
  40. });
  41. this.spec.dataSources = new DataSourcesModel(this.spec.dataSources);
  42. var dataSetShapingsToUpgrade = _.filter(this.spec.datasetShaping, function (datasetShapingInst) {
  43. return datasetShapingInst.calculations.length;
  44. });
  45. this._buildEventGroupMap();
  46. return this._upgradeShaping(dataSetShapingsToUpgrade).then(this._upgradeFilters.bind(this)).then(this._upgradeShapingFilters.bind(this)).then(function () {
  47. this._updateWidgetCalculationReferences();
  48. // remove old shaping
  49. delete this.spec.datasetShaping;
  50. // For pin upgrade with need the JSON back, not the object
  51. if (this.data.pinUpgrade) {
  52. if (this.spec.dataSources.toJSON) {
  53. this.spec.dataSources = this.spec.dataSources.toJSON();
  54. }
  55. // Cleanup any temp modules that were created
  56. this.dataSourcesService.destroy();
  57. }
  58. Promise.resolve(this.spec);
  59. }.bind(this), function (failure) {
  60. if (this.data.pinUpgrade) {
  61. // Cleanup any temp modules that were created
  62. this.dataSourcesService.destroy();
  63. }
  64. throw failure;
  65. });
  66. },
  67. /**
  68. * @returns container Page id containing the layout id
  69. * @param widgetId
  70. */
  71. getContainerPageId: function getContainerPageId(id) {
  72. var topParent = this.findTopLevelParentItem(this.spec.layout, id);
  73. if (topParent) {
  74. return topParent.id;
  75. }
  76. return undefined;
  77. },
  78. /**
  79. * Given any layout item, find its top level parent item (the item in the layout’s root level item list that contains it).
  80. * @param layout - this is the layout specification in the board spec
  81. * @param id - the id of the model for a widget
  82. * @returns the parent object (ie tab) for this model id
  83. */
  84. findTopLevelParentItem: function findTopLevelParentItem(layout, id) {
  85. var itemFound = null;
  86. _.each(layout.items, function (item) {
  87. if (item.id === id) {
  88. itemFound = layout;
  89. } else if (this.findChildItem(item.items, id) !== null) {
  90. itemFound = item;
  91. }
  92. }.bind(this));
  93. return itemFound;
  94. },
  95. /**
  96. * Recursively search all layout items and their child items lists for an id which matches
  97. * the specified id
  98. * @param items - the items list whose subtree of items lists are to be searched
  99. * @param id - the id to search for.
  100. * @returns the item if found or null if this id does not exist in this items list or any child lists.
  101. */
  102. findChildItem: function findChildItem(items, id) {
  103. if (!items) {
  104. return null;
  105. }
  106. var result = null;
  107. for (var i = 0; i < items.length; ++i) {
  108. var item = items[i];
  109. if (item.id === id) {
  110. // found it!
  111. result = item;
  112. break;
  113. } else if (item.items) {
  114. item = this.findChildItem(item.items, id);
  115. if (item) {
  116. // found it!
  117. result = item;
  118. break;
  119. }
  120. // otherwise continue with the remaining siblings
  121. }
  122. }
  123. return result;
  124. },
  125. _buildEventGroupMap: function _buildEventGroupMap() {
  126. _.each(this.spec.eventGroups, function (eg) {
  127. _.each(eg.widgetIds, function (wIds) {
  128. this._eventGroupMap[wIds] = eg.id;
  129. }.bind(this));
  130. }.bind(this));
  131. },
  132. _upgradeFilters: function _upgradeFilters() {
  133. try {
  134. var pageContextMap = {};
  135. var promises = [];
  136. _.each(this.spec.widgets, function (widget) {
  137. var modelRef = widget && widget.data && widget.data.dataViews[0] ? widget.data.dataViews[0].modelRef : null;
  138. if (modelRef) {
  139. var sourceSpec = _.find(this.spec.dataSources.sources.models, function (source) {
  140. return source.id === modelRef;
  141. });
  142. promises.push(this._getModule(sourceSpec, false).then(function (module) {
  143. if (!this.spec.pageContext) {
  144. this.spec.pageContext = [];
  145. }
  146. if (!_.isEmpty(widget.filters)) {
  147. this._buildPageContext(module, pageContextMap, widget, sourceSpec.id);
  148. delete widget.filters;
  149. }
  150. if (!_.isEmpty(widget.localFilters)) {
  151. this._updateLocalFilters(module, widget.localFilters);
  152. }
  153. }.bind(this)));
  154. }
  155. }.bind(this));
  156. return Promise.all(promises).then(function () {
  157. if (!_.isEmpty(pageContextMap)) {
  158. this.spec.pageContext = _.values(pageContextMap);
  159. }
  160. }.bind(this));
  161. } catch (error) {
  162. throw error;
  163. }
  164. },
  165. _upgradeShapingFilters: function _upgradeShapingFilters() {
  166. try {
  167. var pageContextMap = {};
  168. var promises = [];
  169. _.each(this.spec.datasetShaping, function (shaping) {
  170. var sourceSpec = _.find(this.spec.dataSources.sources.models, function (source) {
  171. return source.assetId === shaping.id;
  172. });
  173. if (sourceSpec) {
  174. promises.push(this._getModule(sourceSpec, false).then(function (module) {
  175. if (!this.spec.pageContext) {
  176. this.spec.pageContext = [];
  177. }
  178. if (!_.isEmpty(shaping.filters)) {
  179. this._addShapingFiltersToPageContext(module, pageContextMap, shaping, sourceSpec.id);
  180. }
  181. delete shaping.filters;
  182. }.bind(this)));
  183. }
  184. }.bind(this));
  185. return Promise.all(promises).then(function () {
  186. if (!_.isEmpty(pageContextMap)) {
  187. this.spec.pageContext = this.spec.pageContext.concat(_.values(pageContextMap));
  188. }
  189. }.bind(this));
  190. } catch (error) {
  191. throw error;
  192. }
  193. },
  194. _updateLocalFilters: function _updateLocalFilters(module, filters) {
  195. try {
  196. _.each(filters, function (filter) {
  197. if (filter.values) {
  198. if (filter.values[0] && _.isObject(filter.values[0]) && !filter.values[0].operator) {
  199. this._updateFilterUseValues(module, filter, 'object');
  200. } else if (filter.values[0] && typeof filter.values[0] === 'string') {
  201. this._updateFilterUseValues(module, filter, 'string');
  202. } else if (!filter.values[0]) {
  203. this._updateFilterUseValues(module, filter, 'null');
  204. } else {
  205. this._updateLocalFilters(module, filter.values);
  206. }
  207. }
  208. }.bind(this));
  209. } catch (error) {
  210. throw error;
  211. }
  212. },
  213. _addShapingFiltersToPageContext: function _addShapingFiltersToPageContext(module, pageContextMap, shape, sourceId) {
  214. _.each(shape.filters, function (filter) {
  215. var mappedProps = this._createTuplesAndHierarchies(module, filter, this.ORIGIN_FILTER);
  216. var pageContext = {
  217. 'origin': this.ORIGIN_FILTER,
  218. 'table': '',
  219. 'alias': '',
  220. 'sourceId': sourceId,
  221. 'scope': 'global',
  222. 'hierarchyNames': mappedProps.hierarchyNames,
  223. 'hierarchyUniqueNames': mappedProps.hierarchies
  224. };
  225. if (filter.operator === 'between' || filter.operator === 'notbetween' || filter.operator === 'lt' || filter.operator === 'gt') {
  226. pageContext.conditions = this._createCondition(filter);
  227. } else {
  228. if (filter.operator === 'notin') {
  229. pageContext.exclude = true;
  230. }
  231. pageContext.tupleSet = mappedProps.tupleSet;
  232. }
  233. var uniqueKey = mappedProps.key + pageContext.scope + pageContext.sourceId;
  234. if (_.isEmpty(pageContextMap[uniqueKey])) {
  235. pageContextMap[uniqueKey] = pageContext;
  236. }
  237. }.bind(this));
  238. },
  239. _buildPageContext: function _buildPageContext(module, pageContextMap, widget, sourceId) {
  240. _.each(widget.filters, function (filter) {
  241. var mappedProps = this._createTuplesAndHierarchies(module, filter, this.ORIGIN_VISUALIZATION);
  242. var pageContext = {
  243. 'origin': this.ORIGIN_VISUALIZATION,
  244. 'table': '',
  245. 'alias': '',
  246. 'tupleSet': mappedProps.tupleSet,
  247. 'sourceId': sourceId,
  248. 'hierarchies': mappedProps.hierarchyList,
  249. 'hierarchyUniqueNames': mappedProps.hierarchies,
  250. 'scope': this.getContainerPageId(widget.id),
  251. 'eventSourceId': widget.id,
  252. 'eventGroupId': this._eventGroupMap[widget.id]
  253. };
  254. var uniqueKey = mappedProps.key + pageContext.eventGroupId + pageContext.scope + pageContext.sourceId;
  255. if (_.isEmpty(pageContextMap[uniqueKey])) {
  256. pageContextMap[uniqueKey] = pageContext;
  257. }
  258. }.bind(this));
  259. },
  260. _createCondition: function _createCondition(filter) {
  261. var condition = {
  262. from: '',
  263. to: '',
  264. attributeUniqueNames: [filter.columnId]
  265. };
  266. if (filter.operator === 'between' || filter.operator === 'notbetween') {
  267. condition.from = [filter.values[0].d || filter.values[0].displayValue || filter.values[0]];
  268. condition.to = [filter.values[1].d || filter.values[1].displayValue || filter.values[1]];
  269. } else if (filter.operator === 'lt') {
  270. condition.to = [filter.values[0].d || filter.values[0].displayValue || filter.values[0]];
  271. } else {
  272. condition.from = [filter.values[0].d || filter.values[0].displayValue || filter.values[0]];
  273. }
  274. if (filter.operator === 'notbetween') {
  275. condition.invert = true;
  276. }
  277. return [condition];
  278. },
  279. _createTuplesAndHierarchies: function _createTuplesAndHierarchies(module, filter, origin) {
  280. var properties;
  281. switch (filter.operator) {
  282. case 'in':
  283. case 'notin':
  284. case 'isnull':
  285. case 'between':
  286. case 'notbetween':
  287. case 'lt':
  288. case 'gt':
  289. this._updateFilterUseValues(module, filter);
  290. properties = this._buildEdgeFilterTupleSet(module, filter, origin);
  291. break;
  292. case 'or':
  293. case 'not':
  294. properties = this._buildDataPointFilterTupleSet(module, filter, origin);
  295. break;
  296. default:
  297. properties = {
  298. 'tupleSet': '',
  299. 'hierachyList': '',
  300. 'hierarchies': ''
  301. };
  302. break;
  303. }
  304. return properties;
  305. },
  306. _getMetadataColumn: function _getMetadataColumn(module, columnId) {
  307. return module ? module.getMetadataColumn(columnId) : null;
  308. },
  309. _getMetadataColumnLabel: function _getMetadataColumnLabel(mdColumn) {
  310. return mdColumn ? mdColumn.getLabel() : null;
  311. },
  312. isFacetEnabled: function isFacetEnabled(mdColumn) {
  313. var facetDefinition = mdColumn && mdColumn.getFacetDefinition ? mdColumn.getFacetDefinition() : null;
  314. return facetDefinition && facetDefinition.enabled && facetDefinition.enabled.enumValue !== 'false' ? true : false;
  315. },
  316. isOlapPackage: function isOlapPackage(mdColumn) {
  317. var retval = false;
  318. if (mdColumn) {
  319. var sourceCategory = mdColumn.getSourceCategory();
  320. retval = sourceCategory && sourceCategory !== 'column' && mdColumn.getObjectType() === 'QueryItem';
  321. }
  322. return retval;
  323. },
  324. _isDateOrTimeValue: function _isDateOrTimeValue(dateStr) {
  325. return isNaN(dateStr) && !isNaN(Date.parse(dateStr));
  326. },
  327. _isDateOrTime: function _isDateOrTime(metadata, value) {
  328. return metadata && metadata.isDateOrTimeType() && this._isDateOrTimeValue(value);
  329. },
  330. _isRangeOperator: function _isRangeOperator(operator) {
  331. return operator && (operator === 'between' || operator === 'notbetween' || operator === 'lt' || operator === 'gt');
  332. },
  333. _isRangeFilter: function _isRangeFilter(metadataColumn, filter, value) {
  334. return this._isRangeOperator(filter.operator) && (_.isNumber(value) || this._isDateOrTime(metadataColumn, value));
  335. },
  336. _updateFilterUseValues: function _updateFilterUseValues(module, filter, type) {
  337. var metadataColumn = this._getMetadataColumn(module, filter.columnId);
  338. var facetEnabled = this.isFacetEnabled(metadataColumn);
  339. var isOlap = this.isOlapPackage(metadataColumn);
  340. if (filter && filter.values) {
  341. var updateValuesList = [];
  342. _.each(filter.values, function (value) {
  343. var mappedObj = {};
  344. if (type === 'string') {
  345. mappedObj.u = value;
  346. mappedObj.d = value;
  347. } else if (type === 'null') {
  348. mappedObj = null;
  349. } else {
  350. mappedObj.u = !facetEnabled || isOlap || this._isRangeFilter(metadataColumn, filter, value.useValue) || value.useValue === null ? value.useValue : this._mapUseValue(filter.columnId, value.useValue);
  351. mappedObj.d = value.displayValue;
  352. }
  353. updateValuesList.push(mappedObj);
  354. }.bind(this));
  355. delete filter.values;
  356. filter.values = updateValuesList;
  357. }
  358. },
  359. _mapUseValue: function _mapUseValue(hun, value) {
  360. value = typeof value === 'string' ? value.replace(/]/g, ']]') : value;
  361. return hun + '->' + '[' + value + ']';
  362. },
  363. _buildEdgeFilterTupleSet: function _buildEdgeFilterTupleSet(module, filter, origin) {
  364. var hunKey = '';
  365. var tupleSet = {};
  366. var hierarchyList = [];
  367. var hierarchies = [];
  368. var hierarchyNames = [];
  369. if (origin === this.ORIGIN_FILTER) {
  370. hierarchyNames.push(this._getMetadataColumnLabel(this._getMetadataColumn(module, filter.columnId)));
  371. } else {
  372. hierarchyList.push({
  373. 'hierarchyUniqueName': filter.columnId
  374. });
  375. }
  376. _.each(filter.values, function (value) {
  377. tupleSet[value.u] = value;
  378. hunKey += filter.columnId;
  379. hierarchies.push(filter.columnId);
  380. });
  381. var filterProps = {};
  382. filterProps.tupleSet = JSON.stringify(tupleSet);
  383. filterProps.hierarchies = _.uniq(hierarchies);
  384. filterProps.key = hunKey;
  385. if (origin === this.ORIGIN_FILTER) {
  386. filterProps.hierarchyNames = hierarchyNames;
  387. } else if (origin === this.ORIGIN_VISUALIZATION) {
  388. filterProps.hierarchyList = hierarchyList;
  389. }
  390. return filterProps;
  391. },
  392. _buildDataPointFilterTupleSet: function _buildDataPointFilterTupleSet(module, filter) {
  393. var hunKey = '';
  394. var tupleSet = {};
  395. var hierarchyList = [];
  396. var hierarchies = [];
  397. if (filter.values) {
  398. _.each(filter.values, function (filters) {
  399. if (filters.operator === 'and') {
  400. this._buildAndFilterTuple(module, tupleSet, hierarchyList, filters);
  401. }
  402. }.bind(this));
  403. }
  404. hierarchyList = _.uniq(hierarchyList);
  405. _.each(hierarchyList, function (h) {
  406. hierarchies.push({
  407. 'hierarchyUniqueName': h
  408. });
  409. hunKey += h;
  410. });
  411. return {
  412. 'tupleSet': JSON.stringify(tupleSet),
  413. 'hierarchyList': _.uniq(hierarchies),
  414. 'hierarchies': _.uniq(hierarchyList),
  415. 'key': hunKey
  416. };
  417. },
  418. _buildAndFilterTuple: function _buildAndFilterTuple(module, tupleSet, hierarchyList, filter) {
  419. var tupleKey = '';
  420. var tupleValues = [];
  421. _.each(filter.values, function (value) {
  422. this._updateFilterUseValues(module, value);
  423. _.each(value.values, function (value) {
  424. tupleKey += value.u;
  425. tupleValues.push(value);
  426. }.bind(this));
  427. hierarchyList.push(value.columnId);
  428. }.bind(this));
  429. tupleSet[tupleKey] = tupleValues;
  430. },
  431. _upgradeShaping: function _upgradeShaping(datasetShapingsToUpgrade) {
  432. try {
  433. var datasetShapingInst = datasetShapingsToUpgrade.shift();
  434. if (datasetShapingInst) {
  435. return this._upgradeShapingInstance(datasetShapingInst).then(function () {
  436. this.spec.queriedForUpgrade = true;
  437. return this._upgradeShaping(datasetShapingsToUpgrade);
  438. }.bind(this));
  439. } else {
  440. return Promise.resolve(true);
  441. }
  442. } catch (error) {
  443. throw error;
  444. }
  445. },
  446. _getSourcesCollection: function _getSourcesCollection(dataSourceModel) {
  447. var sourcesCollection = this.dataSourcesService.getSourcesCollection(dataSourceModel);
  448. return sourcesCollection.getSources();
  449. },
  450. _getModule: function _getModule(sourceSpec, forceTemp) {
  451. if (!sourceSpec) {
  452. throw new Error('Could not find source specfication');
  453. }
  454. if (forceTemp === true) {
  455. sourceSpec.useTempModule = true;
  456. }
  457. var sources = this._getSourcesCollection(this.spec.dataSources);
  458. var existingSource = _.find(sources, function (source) {
  459. return source.getAssetId() === sourceSpec.assetId;
  460. });
  461. if (!existingSource) {
  462. throw new Error('Could not find source module');
  463. } else {
  464. var showErrorToast = typeof this.data.showErrorToast === 'undefined' ? true : this.data.showErrorToast;
  465. return existingSource.getModule(null, showErrorToast);
  466. }
  467. },
  468. _upgradeShapingInstance: function _upgradeShapingInstance(datasetShapingInst) {
  469. try {
  470. var sourceSpec = _.find(this.spec.dataSources.sources.models, function (source) {
  471. return source.assetId === datasetShapingInst.id;
  472. });
  473. if (!sourceSpec) {
  474. return Promise.resolve(true);
  475. }
  476. return this._getModule(sourceSpec, true).then(function (module) {
  477. return this._addCalculation(module, datasetShapingInst.calculations);
  478. }.bind(this), function (failure) {
  479. if (this.data.pinUpgrade) {
  480. // Cleanup any temp modules that were created
  481. this.dataSourcesService.destroy();
  482. }
  483. throw failure;
  484. }.bind(this));
  485. } catch (error) {
  486. throw error;
  487. }
  488. },
  489. _addCalculation: function _addCalculation(module, calculations) {
  490. var calculation = calculations.shift();
  491. if (calculation) {
  492. // For percent change, the new opereation to use is ¢
  493. var operation = calculation.expr.op === '%d' ? '¢' : calculation.expr.op;
  494. var calcProperties = {
  495. inputtedName: calculation.label,
  496. operation: operation,
  497. elementOperands: [],
  498. numberOperands: []
  499. };
  500. _.each(calculation.expr.params, function (param) {
  501. var metaDataColumn = module.getMetadataColumn(this._calcIdMap[param.col] || param.col);
  502. calcProperties.elementOperands.push(metaDataColumn.moserObject);
  503. }.bind(this));
  504. // For old % change calculation we need to reverse the array of data items or we won't get the same numbers as in R6.
  505. if (operation === '¢') {
  506. calcProperties.elementOperands.reverse();
  507. }
  508. return module.addCalculation(calcProperties).then(function (newCalculationId) {
  509. this._calcIdMap[calculation.id] = newCalculationId;
  510. return this._addCalculation(module, calculations);
  511. }.bind(this));
  512. } else {
  513. return Promise.resolve(true);
  514. }
  515. },
  516. _updateWidgetCalculationReferences: function _updateWidgetCalculationReferences() {
  517. try {
  518. _.each(this.spec.widgets, function (widget) {
  519. var dataViews = widget.data && widget.data.dataViews ? widget.data.dataViews : [];
  520. _.each(dataViews, function (dataView) {
  521. _.each(dataView.dataItems, function (dataItem) {
  522. dataItem.itemId = this._calcIdMap[dataItem.itemId] || dataItem.itemId;
  523. }.bind(this));
  524. }.bind(this));
  525. _.each(widget.localFilters, function (localFilter) {
  526. localFilter.columnId = this._calcIdMap[localFilter.columnId] || localFilter.columnId;
  527. }.bind(this));
  528. }.bind(this));
  529. } catch (error) {
  530. throw error;
  531. }
  532. },
  533. down: function down(spec) {
  534. return Promise.resolve(spec);
  535. }
  536. });
  537. return new UpgradeShaping();
  538. });
  539. //# sourceMappingURL=waca_shaping.js.map