SmartsRecommenderFeature.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. 'use strict';
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2018, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['./Utils', './VisRecommenderDelegator', '../../../api/impl/Visualization', '../../../widgets/livewidget/models/LiveWidgetModel'], function (Utils, VisRecommenderDelegator, Visualization, LiveWidgetModel) {
  9. 'use strict';
  10. var VisualizationSmartsRecommender = function () {
  11. function VisualizationSmartsRecommender(_ref) {
  12. var features = _ref.features;
  13. _classCallCheck(this, VisualizationSmartsRecommender);
  14. this.features = features;
  15. this.transaction = features['Transaction'];
  16. this.state = features['state.internal'];
  17. this.logger = features['Logger'];
  18. this.visDefinitions = this.features['VisDefinitions'];
  19. this.moserDataSources = this.features['DataSources.moser'];
  20. this._visRecommenderDelegator = new VisRecommenderDelegator({
  21. dataSources: this.features['DataSources'],
  22. moserDataSources: this.moserDataSources,
  23. visDefinitions: this.visDefinitions,
  24. dashboardApi: this.features['Dashboard.API'],
  25. logger: this.logger
  26. });
  27. }
  28. VisualizationSmartsRecommender.prototype.destroy = function destroy() {
  29. this.features = null;
  30. this.transaction = null;
  31. this.state = null;
  32. this.logger = null;
  33. this.visDefinitions = null;
  34. this.creationTransactionToken = null;
  35. this._visRecommenderDelegator = null;
  36. };
  37. VisualizationSmartsRecommender.prototype.getAPI = function getAPI(type) {
  38. if (type === 'deprecated') {
  39. return this._deprecatedAPI;
  40. }
  41. return this._api;
  42. };
  43. VisualizationSmartsRecommender.prototype.isValidDataSource = function isValidDataSource(visualization) {
  44. return visualization.getDataSource() && visualization.getDataSource().getState() !== 'error';
  45. };
  46. VisualizationSmartsRecommender.prototype.getRecommendedVisualizations = function getRecommendedVisualizations(visualization) {
  47. var _this = this;
  48. if (this.isValidDataSource(visualization) && visualization.getSlots().isMappingComplete()) {
  49. return this.recommendAlternateVisualizations(visualization).then(function (recommendations) {
  50. return recommendations.map(function (recommendation) {
  51. return _this.visDefinitions.getById(recommendation.visId);
  52. });
  53. });
  54. } else {
  55. return Promise.resolve([]);
  56. }
  57. };
  58. VisualizationSmartsRecommender.prototype.getRecommendedTitle = function getRecommendedTitle(visualization) {
  59. if (this.isValidDataSource(visualization)) {
  60. return this._visRecommenderDelegator.getRecommendInfo(visualization).then(function (info) {
  61. return info.label;
  62. }).catch(function (error) {
  63. return Promise.reject(new Error(error));
  64. });
  65. } else {
  66. return Promise.resolve();
  67. }
  68. };
  69. VisualizationSmartsRecommender.prototype.setType = function setType(visualization, type, transactionToken) {
  70. var _this2 = this;
  71. var subTransaction = this.transaction.startTransaction(transactionToken);
  72. var newDefinition = this.visDefinitions.getByType(type);
  73. if (newDefinition) {
  74. if (!visualization.isTypeLocked()) {
  75. visualization.lockType(subTransaction);
  76. }
  77. if (this.isValidDataSource(visualization)) {
  78. return this._visRecommenderDelegator.recommendBindingsForSelectedViz(visualization, newDefinition.getId()).then(function (recommendation) {
  79. _this2._applyRecommendationToVisualization(visualization, recommendation, subTransaction);
  80. }).finally(function () {
  81. _this2.transaction.endTransaction(subTransaction);
  82. });
  83. } else {
  84. visualization.setType(type, subTransaction);
  85. this.transaction.endTransaction(subTransaction);
  86. return Promise.resolve();
  87. }
  88. }
  89. throw new Error('Invalid definition');
  90. };
  91. VisualizationSmartsRecommender.prototype.addDataItems = function addDataItems(visualization, dataItemSpecList, transactionToken) {
  92. var _this3 = this;
  93. var dataSource = visualization.getDataSource();
  94. if (!dataSource) {
  95. throw new Error('You must set a datasource in the visualization feature first');
  96. }
  97. var columnIds = dataItemSpecList.map(function (dataItemSpec) {
  98. return dataItemSpec.columnId;
  99. });
  100. var subTransactionToken = this.transaction.startTransaction(transactionToken);
  101. if (visualization.isTypeLocked()) {
  102. return this._visRecommenderDelegator.recommendBindingsForNewColumns(visualization, columnIds).then(function (recommendation) {
  103. _this3._applyRecommendationToVisualization(visualization, recommendation, subTransactionToken);
  104. }).finally(function () {
  105. _this3.transaction.endTransaction(subTransactionToken);
  106. });
  107. } else {
  108. return this._visRecommenderDelegator.recommendBestAlternateVisualization(visualization, columnIds).then(function (recommendation) {
  109. if (_this3.features.LiveWidgetSegment) {
  110. _this3.features.LiveWidgetSegment.track({ visualization: visualization, info: recommendation, category: 'fromRecommendation' });
  111. }
  112. _this3._applyRecommendationToVisualization(visualization, recommendation, subTransactionToken);
  113. }).finally(function () {
  114. _this3.transaction.endTransaction(subTransactionToken);
  115. });
  116. }
  117. };
  118. VisualizationSmartsRecommender.prototype.recommendBestVisualization = function recommendBestVisualization(visualization, transactionToken) {
  119. var _this4 = this;
  120. if (this.isValidDataSource(visualization)) {
  121. var subTransaction = this.transaction.startTransaction(transactionToken);
  122. if (visualization.isTypeLocked()) {
  123. visualization.unlockType(subTransaction);
  124. }
  125. return this._visRecommenderDelegator.recommendBestAlternateVisualization(visualization).then(function (recommendation) {
  126. if (_this4.features.LiveWidgetSegment) {
  127. _this4.features.LiveWidgetSegment.track({ visualization: visualization, info: recommendation, category: 'fromRecommendation' });
  128. }
  129. _this4._applyRecommendationToVisualization(visualization, recommendation, subTransaction);
  130. }).finally(function () {
  131. _this4.transaction.endTransaction(subTransaction);
  132. });
  133. }
  134. return Promise.resolve();
  135. };
  136. VisualizationSmartsRecommender.prototype.recommendAlternateVisualizations = function recommendAlternateVisualizations(visualization) {
  137. var result = void 0;
  138. if (this.isValidDataSource(visualization)) {
  139. var dataSource = visualization.getDataSource();
  140. var module = this.moserDataSources.getModule(dataSource.getId());
  141. var columnIds = Utils.getUnboundColumnsIds(visualization);
  142. result = this._visRecommenderDelegator.recommendAlternateVisualizations(columnIds, {
  143. assetId: module.getAssetId(),
  144. sourceType: module.getSourceType(),
  145. module: module
  146. });
  147. } else {
  148. result = Promise.resolve([]);
  149. }
  150. return result;
  151. };
  152. VisualizationSmartsRecommender.prototype.recommendAlternateVisualizationsAsWidgetSpecs = function recommendAlternateVisualizationsAsWidgetSpecs(visualization, baseWidgetSpec) {
  153. var _this5 = this;
  154. return this.recommendAlternateVisualizations(visualization).then(function (recommendations) {
  155. return _this5._transformWidgetRecommendationsToLiveWidgetSpecs(recommendations, baseWidgetSpec);
  156. });
  157. };
  158. VisualizationSmartsRecommender.prototype.recommendRelatedVisualizations = function recommendRelatedVisualizations(visualization) {
  159. var result = void 0;
  160. if (this.isValidDataSource(visualization)) {
  161. var dataSource = visualization.getDataSource();
  162. var columnIds = Utils.getUnboundColumnsIds(visualization);
  163. var topBottomItems = Utils.getTopBottomItems(visualization);
  164. var filterItems = Utils.getFilterItems(visualization);
  165. result = this.recommendRelatedVisualizationsFromColumnIds(dataSource.getId(), columnIds, [].concat(topBottomItems, filterItems));
  166. } else {
  167. result = Promise.resolve([]);
  168. }
  169. return result;
  170. };
  171. VisualizationSmartsRecommender.prototype.recommendAlternateVisualizationsFromColumnIds = function recommendAlternateVisualizationsFromColumnIds(dataSourceId, columnIds) {
  172. var module = this.moserDataSources.getModule(dataSourceId);
  173. return this._visRecommenderDelegator.recommendAlternateVisualizations(columnIds, {
  174. assetId: module.getAssetId(),
  175. sourceType: module.getSourceType(),
  176. module: module
  177. });
  178. };
  179. VisualizationSmartsRecommender.prototype.recommendRelatedVisualizationsFromColumnIds = function recommendRelatedVisualizationsFromColumnIds(dataSourceId, columnIds, filters, includeBestVisForTargetFields) {
  180. var module = this.moserDataSources.getModule(dataSourceId);
  181. return this._visRecommenderDelegator.recommendRelatedVisualizations(columnIds, {
  182. assetId: module.getAssetId(),
  183. sourceType: module.getSourceType(),
  184. module: module,
  185. filters: filters,
  186. includeBestVisForTargetFields: !!includeBestVisForTargetFields,
  187. moserDataSources: this.moserDataSources,
  188. visualization: this.visualization
  189. });
  190. };
  191. // deprecated interface -- called by explore
  192. VisualizationSmartsRecommender.prototype.recommendRelatedVisualizationsAsWidgetSpecs = function recommendRelatedVisualizationsAsWidgetSpecs(visualization, baseWidgetSpec) {
  193. var _this6 = this;
  194. return this.recommendRelatedVisualizations(visualization).then(function (recommendations) {
  195. return _this6._transformWidgetRecommendationsToLiveWidgetSpecs(recommendations, baseWidgetSpec);
  196. });
  197. };
  198. VisualizationSmartsRecommender.prototype.recommendCompareVisualizationsAsWidgetSpecs = function recommendCompareVisualizationsAsWidgetSpecs(visualization, widgetSpec) {
  199. var result = void 0;
  200. if (this.isValidDataSource(visualization)) {
  201. var dataSource = visualization.getDataSource();
  202. var module = this.moserDataSources.getModule(dataSource.getId());
  203. var options = {};
  204. options.module = module;
  205. options.columnIds = Utils.getUnboundColumnsIds(visualization);
  206. options.widgetSpec = widgetSpec;
  207. options.visualization = visualization;
  208. options.assetId = dataSource.getAssetId();
  209. options.sourceType = dataSource.getType();
  210. options.moserDataSources = this.moserDataSources;
  211. result = this._visRecommenderDelegator.recommendCompareVisualizations(options.columnIds, options);
  212. } else {
  213. result = Promise.resolve([]);
  214. }
  215. return result;
  216. };
  217. VisualizationSmartsRecommender.prototype._transformWidgetRecommendationsToLiveWidgetSpecs = function _transformWidgetRecommendationsToLiveWidgetSpecs(recommendations, basewidgetSpec) {
  218. var _this7 = this;
  219. var promises = [];
  220. var results = [];
  221. recommendations.forEach(function (recommendation) {
  222. basewidgetSpec = JSON.parse(JSON.stringify(basewidgetSpec));
  223. // Shoud be done throught the API -- but we need to be able to easily create a content API
  224. basewidgetSpec.name = recommendation.label;
  225. // Should be done through the filter API once it is ready
  226. if (recommendation.filters && recommendation.filters.length > 0) {
  227. basewidgetSpec.localFilters = recommendation.filters;
  228. }
  229. var widgetModelCopy = new LiveWidgetModel(basewidgetSpec);
  230. var visualization = new Visualization({
  231. doNotRegisterDataSourceUsage: true,
  232. content: _this7.content,
  233. features: {
  234. 'Models.internal': {
  235. getWidgetModel: function getWidgetModel() {
  236. return widgetModelCopy;
  237. }
  238. },
  239. 'Dashboard.API': _this7.features['Dashboard.API'],
  240. 'Dashboard.Logger': _this7.features['Logger'],
  241. 'Dashboard.Colors': _this7.features['Colors'],
  242. 'Dashboard.Transaction': _this7.features['Transaction'],
  243. 'Dashboard.DataSources': _this7.features['DataSources'],
  244. 'Dashboard.internal': _this7.features['Dashboard.internal'],
  245. 'Dashboard.VisDefinitions': _this7.features['VisDefinitions'],
  246. 'Dashboard.VisDefinitions.internal': _this7.features['VisDefinitions.internal']
  247. }
  248. });
  249. promises.push(visualization.initialize().then(function () {
  250. visualization.unlockType();
  251. _this7._applyRecommendationToVisualization(visualization, recommendation);
  252. // strip the thumbnailId from the base model
  253. var spec = widgetModelCopy.toJSON([], ['thumbnailId']);
  254. // return recommendation specs without ids will generate ids at the addition time
  255. delete spec.id;
  256. // TODO -- why this ?
  257. // the conversation expects a recommenderInfo for some reason..
  258. spec.recommenderInfo = {
  259. rtitle: recommendation.title,
  260. description: recommendation.description,
  261. label: recommendation.label
  262. };
  263. results.push({
  264. name: widgetModelCopy.get('name'),
  265. spec: spec
  266. });
  267. }));
  268. });
  269. console.log('recommendations... ', results);
  270. return Promise.all(promises).then(function () {
  271. return results;
  272. });
  273. };
  274. VisualizationSmartsRecommender.prototype._applyRecommendationToVisualization = function _applyRecommendationToVisualization(visualization, recommendation, transactionToken) {
  275. var subTransaction = this.transaction.startTransaction(transactionToken);
  276. // Get the original list of dataItems mapped to slots before the visId change.
  277. var originalSlots = visualization.getSlots();
  278. var originalMappedDataItemIdList = originalSlots.getMappingInfoList().map(function (diInfo) {
  279. return diInfo.dataItem && diInfo.dataItem.getId();
  280. });
  281. // Check if we are changing the viz
  282. if (recommendation.visId !== visualization.getDefinition().getId()) {
  283. var newDef = this.visDefinitions.getById(recommendation.visId);
  284. visualization.setType(newDef.getType(), subTransaction);
  285. }
  286. var slots = visualization.getSlots();
  287. var unmappedDataItemList = [];
  288. var mappedDataItemListBySlot = {};
  289. slots.getDataItemList().forEach(function (dataItem) {
  290. var mapping = slots.getMappingInfo(dataItem.getId());
  291. if (!mapping) {
  292. unmappedDataItemList.push(dataItem);
  293. } else {
  294. var slotId = mapping.slot.getId();
  295. if (!mappedDataItemListBySlot[slotId]) {
  296. mappedDataItemListBySlot[slotId] = [];
  297. }
  298. mappedDataItemListBySlot[slotId].push(dataItem);
  299. }
  300. });
  301. var recommendedColumns = {};
  302. for (var slotName in recommendation.slots) {
  303. recommendation.slots[slotName].forEach(function (value) {
  304. recommendedColumns[value] = true;
  305. });
  306. }
  307. // Check if the recommender has changed some mapping to a different slot. If so, we move the dataItem to the unmapped datsaItems so that it can be
  308. // picked by another slot
  309. var slotList = slots.getSlotList();
  310. slotList.forEach(function (slot) {
  311. var name = slot.getId();
  312. var dataItems = slot.getDataItemList(true);
  313. dataItems.forEach(function (dataItem) {
  314. if (!recommendation.slots[name] || !recommendation.slots[name].find(function (columnId) {
  315. return dataItem.getColumnId() === columnId;
  316. })) {
  317. unmappedDataItemList.push(dataItem);
  318. }
  319. });
  320. });
  321. var newMappingBySlot = {};
  322. slotList.forEach(function (slot) {
  323. var name = slot.getId();
  324. var slotId = slot.getId();
  325. var dataItemsToAdd = [];
  326. if (recommendation.slots[name]) {
  327. recommendation.slots[name].forEach(function (columnId) {
  328. var dataItem = void 0;
  329. var filterDataItems = function filterDataItems(di) {
  330. if (!dataItem && columnId === di.getColumnId()) {
  331. dataItem = di;
  332. return false;
  333. }
  334. return true;
  335. };
  336. // 1 - Search the previous mapped dataitems to this slot (if they exist)
  337. if (mappedDataItemListBySlot[slotId]) {
  338. // We filter to exclude the matched dataitems as we find them
  339. mappedDataItemListBySlot[slotId] = mappedDataItemListBySlot[slotId].filter(filterDataItems);
  340. }
  341. // 2 - Search the unmapped data items
  342. if (!dataItem) {
  343. // Search the unmapped list
  344. unmappedDataItemList = unmappedDataItemList.filter(filterDataItems);
  345. }
  346. // 3 - New column eing added, create a dataImte
  347. if (!dataItem) {
  348. dataItem = slots.createDataItems([{ columnId: columnId }], subTransaction)[0];
  349. }
  350. dataItemsToAdd.push(dataItem.getId());
  351. });
  352. } else {
  353. // slots which are not recommendable should maintain the original mappings
  354. // for example, heat slot for crosstab and table
  355. var unmappedSlot = slots.getSlot(slotId);
  356. var isHidden = unmappedSlot.getDefinition().isHidden();
  357. if (isHidden) {
  358. unmappedSlot.getDataItemList().forEach(function (dataItem) {
  359. if (!recommendedColumns[dataItem.getColumnId()]) {
  360. dataItemsToAdd.push(dataItem.getId());
  361. }
  362. });
  363. }
  364. }
  365. var dataItemList = slot.getDataItemList(true);
  366. var dataItemsToRemove = dataItemList.filter(function (dataItem) {
  367. return dataItem.isColumnUnavailable();
  368. });
  369. if (dataItemsToRemove.length > 0) {
  370. slot.removeDataItems(dataItemsToRemove.map(function (dataItem) {
  371. return dataItem.getId();
  372. }), subTransaction);
  373. }
  374. newMappingBySlot[slotId] = dataItemsToAdd;
  375. slot.removeDataItemsMapping(dataItemList.map(function (dataItem) {
  376. return dataItem.getId();
  377. }), subTransaction);
  378. });
  379. //Place slots which can generate measure groups at the end.
  380. //This means when a measure group is generated in addDataItemsMappings,
  381. //any other data items in the same slot will already exist, so it can be
  382. //placed in the correct position.
  383. var slotIds = Object.keys(newMappingBySlot).map(function (id) {
  384. return slots.getSlot(id);
  385. }).sort(function (slotA, slotB) {
  386. var a = slotA.getDefinition().getProperty('multiMeasure') ? 1 : -1,
  387. b = slotB.getDefinition().getProperty('multiMeasure') ? 1 : -1;
  388. return a - b;
  389. }).map(function (slot) {
  390. return slot.getId();
  391. });
  392. // Add all the new mappings
  393. slotIds.forEach(function (id) {
  394. if (newMappingBySlot[id].length > 0) {
  395. slots.getSlot(id).addDataItemsMapping(newMappingBySlot[id], -1, subTransaction);
  396. }
  397. });
  398. var mappingInfoList = slots.getMappingInfoList();
  399. // remove all leftover top bottom items if smarts returns top bottom items, such as for related-viz case
  400. if (recommendation.topBottom && recommendation.topBottom.length > 0) {
  401. recommendation.topBottom.forEach(function (_ref2) {
  402. var itemId = _ref2.itemId,
  403. spec = _ref2.spec;
  404. var mappingEntry = mappingInfoList.find(function (mappingInfoList) {
  405. return mappingInfoList.dataItem.getColumnId() === itemId;
  406. });
  407. if (mappingEntry) {
  408. var topBottom = {
  409. type: spec.type,
  410. value: spec.value
  411. };
  412. if (spec.context) {
  413. topBottom.context = {
  414. itemId: spec.context,
  415. aggregate: spec.aggregate
  416. };
  417. }
  418. mappingEntry.dataItem.setTopBottom(topBottom, subTransaction);
  419. }
  420. });
  421. }
  422. // Auto Group
  423. (recommendation.autoGroup || []).forEach(function (autoGroupItem) {
  424. var itemId = autoGroupItem.itemId,
  425. groupSize = autoGroupItem.groupSize;
  426. if (autoGroupItem && groupSize) {
  427. var mappingEntry = mappingInfoList.find(function (mappingInfoList) {
  428. return mappingInfoList.dataItem.getColumnId() === itemId;
  429. });
  430. if (mappingEntry) {
  431. mappingEntry.dataItem.setBinning({
  432. bins: groupSize
  433. }, subTransaction);
  434. }
  435. }
  436. });
  437. //Compare the final mapped DataItem list to the original one. Clean any dataItems that are no longer mapped.
  438. var mappedDataItemIdList = slots.getMappingInfoList().map(function (diInfo) {
  439. return diInfo.dataItem && diInfo.dataItem.getId();
  440. });
  441. var unmappedDataItemIdList = originalMappedDataItemIdList.filter(function (originalId) {
  442. return mappedDataItemIdList.indexOf(originalId) === -1;
  443. });
  444. if (unmappedDataItemIdList.length > 0) {
  445. slots.deleteDataItems(unmappedDataItemIdList, subTransaction);
  446. }
  447. this.transaction.endTransaction(subTransaction);
  448. };
  449. return VisualizationSmartsRecommender;
  450. }();
  451. return VisualizationSmartsRecommender;
  452. });
  453. //# sourceMappingURL=SmartsRecommenderFeature.js.map