DashboardSmartsVisRecommenderWrapper.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: Dashboard
  6. *| (C) Copyright IBM Corp. 2018
  7. *|
  8. *| US Government Users Restricted Rights - Use, duplication or disclosure
  9. *| restrictedashboardSpecSvcd by GSA ADP Schedule Contract with IBM Corp.
  10. *+------------------------------------------------------------------------+
  11. */
  12. define(['underscore', './BaseSmartsVisRecommenderWrapper', './IRUtils', '../../apiHelpers/SlotAPIHelper', '../../widgets/livewidget/nls/StringResources', './LegacyMapMappingManager', './SmartsRecommenderError', './VisRecommenderBindingFallback', '../../extensions/smarts-recommender/impl/Utils'], function (_, BaseSmartsVisRecommenderWrapper, IRUtils, SlotAPIHelper, StringResources, LegacyMapMappingManager, SmartsError, VisRecommenderBindingFallback, Utils) {
  13. 'use strict';
  14. /*
  15. * This class is a wrapper for the Smarts Visualization Recommender for dashboard.
  16. */
  17. var DashboardSmartsVisRecommenderWrapper = BaseSmartsVisRecommenderWrapper.extend({
  18. _UnsupportedVisIds: ['list', 'dataPlayer', 'com.ibm.vis.rave2polygonmap'], // not supported by SmartsVisRecommender
  19. _LegacyMapMappingManager: LegacyMapMappingManager, //for unit tests
  20. _VisRecommenderBindingFallback: VisRecommenderBindingFallback,
  21. // constructor
  22. init: function init(_ref) {
  23. var moserDataSources = _ref.moserDataSources,
  24. visDefinitions = _ref.visDefinitions,
  25. logger = _ref.logger,
  26. dashboardApi = _ref.dashboardApi,
  27. dataSources = _ref.dataSources;
  28. this._visDefinitions = visDefinitions;
  29. this._logger = logger;
  30. this._dashboardApi = dashboardApi;
  31. var ajaxSvc = dashboardApi.getGlassCoreSvc('.Ajax');
  32. var glassOptions = {};
  33. dashboardApi.prepareGlassOptions(glassOptions);
  34. DashboardSmartsVisRecommenderWrapper.inherited('init', this, [{ moserDataSources: moserDataSources, ajaxSvc: ajaxSvc, logger: logger, glassOptions: glassOptions, dashboardApi: dashboardApi, dataSources: dataSources }]);
  35. },
  36. destroy: function destroy() {
  37. DashboardSmartsVisRecommenderWrapper.inherited('destroy', this, arguments);
  38. this.moserDataSources = null;
  39. this._visDefinitions = null;
  40. this._logger = null;
  41. this._dashboardApi = null;
  42. },
  43. /**
  44. * Fetch the recommender information for the current chart
  45. * @return {object} payload from smarts server (including `label` attribute)
  46. */
  47. getRecommendInfo: function getRecommendInfo(visualization) {
  48. var _this = this;
  49. var columns = this._getBoundColumnsInfo(visualization);
  50. this._replaceHiddenCustomGroupCols(columns, visualization);
  51. var options = this._getRequestOptions(visualization);
  52. var definition = visualization.getDefinition();
  53. return DashboardSmartsVisRecommenderWrapper.inherited('getRecommendInfo', this, [{
  54. columns: columns,
  55. visId: definition ? definition.getId() : null,
  56. requestOptions: options
  57. }]).then(function (chartInfo) {
  58. if (chartInfo.mandatorySlotsMissing) {
  59. return _this._buildChartInfoFromColumns(columns, options.module);
  60. }
  61. return chartInfo;
  62. }).catch(function (error) {
  63. if (error.message === _this.getUnsupportedVizErrCode()) {
  64. return _this._buildChartInfoFromColumns(columns, options.module);
  65. } else {
  66. return Promise.reject(error);
  67. }
  68. });
  69. },
  70. _replaceHiddenCustomGroupCols: function _replaceHiddenCustomGroupCols(columns, visualization) {
  71. var sourceId = visualization.getDataSource().getId();
  72. var shaping = this._dashboardApi.getFeature('DataSources.moser');
  73. if (shaping && shaping.isConsumerGroupColumn) {
  74. columns.forEach(function (columnEntry) {
  75. var columnIds = columnEntry.columnIds;
  76. columnIds.forEach(function (colId, i) {
  77. if (shaping.isConsumerGroupColumn(sourceId, colId)) {
  78. var customGroup = shaping.getCustomGroupData(sourceId, colId);
  79. if (customGroup && customGroup.basedOnMoserObjectId) {
  80. columnIds[i] = customGroup.basedOnMoserObjectId;
  81. }
  82. }
  83. });
  84. });
  85. }
  86. },
  87. /*
  88. * This works as a local fallback to get smart title for smarts-unsupported visualization types (such as list, legacy map etc)
  89. * By design, we will use the concatenated column labels as the 'smarts title'
  90. */
  91. _buildChartInfoFromColumns: function _buildChartInfoFromColumns(columns, module) {
  92. var aTitle = [];
  93. if (columns && columns.length > 0 && module) {
  94. columns.forEach(function (column) {
  95. column.columnIds.forEach(function (id) {
  96. module.getMetadataColumn(id) && aTitle.push(module.getMetadataColumn(id).getLabel());
  97. });
  98. });
  99. }
  100. var joinedTitles = aTitle.join(StringResources.get('listSeparator') + ' ');
  101. //This could be augmented to create descriptions
  102. return Promise.resolve({
  103. label: joinedTitles,
  104. title: joinedTitles
  105. });
  106. },
  107. /*
  108. * Given a visualization type, recommends the binding for the newly added columns (user added new columns to locked viz type)
  109. * @param {string} visId
  110. * @param {object} array of metadataColumn ids for the newly added columns
  111. * @returns {Object}
  112. */
  113. recommendBindingsForNewColumns: function recommendBindingsForNewColumns(visualization, columnsToBind) {
  114. var _this2 = this;
  115. var visId = visualization.getDefinition().getId();
  116. if (this._isUnsupportedVisId(visId)) {
  117. return this._recommendBindingsForUnsupportedVisId(visualization, visId, columnsToBind);
  118. }
  119. return new Promise(function (resolve) {
  120. var boundColumnsInfo = _this2._getBoundColumnsInfo(visualization);
  121. var requestOptions = _this2._getRequestOptions(visualization);
  122. resolve({
  123. columnsToBind: columnsToBind,
  124. boundColumnsInfo: boundColumnsInfo,
  125. requestOptions: requestOptions,
  126. visId: visId
  127. });
  128. }).then(this.recommendBindings.bind(this)).then(this._handleBindingRecommendationResponse.bind(this, visualization)).catch(this._handleSmartsError.bind(this));
  129. },
  130. /*
  131. * Recommend bindings (aka mappings) for the newly selected visualization (user chose to change viz type) based on columns in the current model.
  132. * @param {string} visId
  133. * @returns {Object}
  134. */
  135. recommendBindingsForSelectedViz: function recommendBindingsForSelectedViz(visualization, visId) {
  136. if (this._isUnsupportedVisId(visId)) {
  137. return this._recommendBindingsForUnsupportedVisId(visualization, visId);
  138. }
  139. var requestParams = {
  140. visId: visId,
  141. requestOptions: this._getRequestOptions(visualization)
  142. };
  143. var definition = visualization.getDefinition();
  144. var originalVisId = definition ? definition.getId() : null;
  145. if (this._isUnsupportedVisId(originalVisId)) {
  146. requestParams.columnsToBind = Utils.getUnboundColumnsIds(visualization);
  147. } else {
  148. requestParams.originalVisId = originalVisId;
  149. requestParams.boundColumnsInfo = this._getBoundColumnsInfo(visualization);
  150. }
  151. return this.recommendBindings(requestParams).then(this._handleBindingRecommendationResponse.bind(this, visualization)).catch(this._handleSmartsError.bind(this));
  152. },
  153. /*
  154. * Returns the best alternate visualization recommendation for the columns in the current model plus
  155. * new columns added (if any).
  156. * @param {newColumns} - optional
  157. * @returns {Object}
  158. */
  159. recommendBestAlternateVisualization: function recommendBestAlternateVisualization(visualization) {
  160. var _this3 = this;
  161. var newColumnsIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  162. return new Promise(function (resolve) {
  163. var columnIds = newColumnsIds.concat(Utils.getUnboundColumnsIds(visualization));
  164. resolve(columnIds);
  165. }).then(function (columnIds) {
  166. return _this3.recommendAlternateVisualizations(columnIds, _this3._getRequestOptions(visualization));
  167. }).then(function (recommendations) {
  168. return recommendations[0];
  169. }).catch(this._handleSmartsError.bind(this));
  170. },
  171. /*
  172. * Assign the slots for visualizations that are not supported by Smarts visRecommender
  173. */
  174. _recommendBindingsForUnsupportedVisId: function _recommendBindingsForUnsupportedVisId(visualization, visId, newColumnsIds) {
  175. switch (visId) {
  176. case 'com.ibm.vis.rave2polygonmap':
  177. {
  178. return this._recommendBindingsForLegacyMap(visualization, visId, newColumnsIds);
  179. }
  180. case 'dataPlayer':
  181. case 'list':
  182. {
  183. return this._recommendBindingsForList_DataPlayer(visualization, visId, newColumnsIds);
  184. }
  185. }
  186. },
  187. _handleSmartsError: function _handleSmartsError(e) {
  188. this._logger.warn('An error occurred in the smart recommender', e);
  189. throw e;
  190. },
  191. _handleBindingRecommendationResponse: function _handleBindingRecommendationResponse(visualization, recommendations) {
  192. var _this4 = this;
  193. /*
  194. * Recommender may return returns > 1 binding recommendations and the recommendation may contain
  195. * unsupported slots or recommendations that do not render in Dashboard/Explore due to required slots not being mapped
  196. * This function filters out the recommendations that contains unsupported slots and pick the best recommendation based on max
  197. * required slots mapped - this will reduce the chance that the visualization is not rendered.
  198. */
  199. //This is a binding request so the visId for all the recommendations are the same, so just use the first one.
  200. return this._getVisDefinition(recommendations[0].visId).then(function (visDefinition) {
  201. //filter out the hidden slots so we dont recommend them
  202. var slotDefinitions = visDefinition.getSlotList().filter(function (slot) {
  203. return !slot.getProperty('hidden');
  204. });
  205. var allSlotIds = slotDefinitions.map(function (dataSlot) {
  206. return dataSlot.getId();
  207. });
  208. var allSlotsAreSupported = function allSlotsAreSupported(recommendation) {
  209. return Object.keys(recommendation.slots).filter(function (slotId) {
  210. return allSlotIds.indexOf(slotId) === -1;
  211. }).length === 0;
  212. };
  213. var requiredSlotIds = visDefinition.getSlotList().filter(function (dataSlot) {
  214. return !dataSlot.getProperty('optional');
  215. }).map(function (dataSlot) {
  216. return dataSlot.getId();
  217. });
  218. // returns the recommendation that has the max required slots for the viz
  219. var maxRequiredSlotsMatched = function maxRequiredSlotsMatched(accumulator, currentRec) {
  220. var maxRequiredSlots = function maxRequiredSlots(recommendation) {
  221. return Object.keys(recommendation.slots).filter(function (slotId) {
  222. return requiredSlotIds.indexOf(slotId) !== -1;
  223. }).length;
  224. };
  225. if (maxRequiredSlots(currentRec) > maxRequiredSlots(accumulator)) {
  226. return currentRec;
  227. }
  228. return accumulator;
  229. };
  230. var recommendation = recommendations.filter(allSlotsAreSupported).reduce(maxRequiredSlotsMatched);
  231. //attempt to manually assign bindings for columns that smarts was unable to bind and show a toast for any that cannot be bound
  232. return _this4._handleUnboundColumns(visualization, recommendation, slotDefinitions);
  233. });
  234. },
  235. _getRequestOptions: function _getRequestOptions(visualization) {
  236. var module = this._getModule(visualization);
  237. return {
  238. assetId: module.getAssetId(),
  239. sourceType: module.getSourceType(),
  240. module: module
  241. };
  242. },
  243. _getVisDefinition: function _getVisDefinition(visId) {
  244. return Promise.resolve(this._visDefinitions.getById(visId));
  245. },
  246. //Return an array of column info with column and (SmartsVisRecommender) slot ids
  247. _getBoundColumnsInfo: function _getBoundColumnsInfo(visualization) {
  248. var mappedSlots = visualization.getSlots().getMappedSlotList();
  249. if (!mappedSlots || !mappedSlots.length) {
  250. return;
  251. }
  252. // ignore hidden slots which are not considered as part of mappings
  253. // e.g. 'rank' slot of grid, 'size' slot of scatter
  254. var columnInfos = mappedSlots.filter(function (slot) {
  255. return slot && !slot.getDefinition().isHidden();
  256. }).map(function (mappedSlot) {
  257. var columnIds = Utils.filterInvalidDataItems(mappedSlot.getDataItemList()).map(function (dataItem) {
  258. return dataItem.getColumnId();
  259. });
  260. var columnInfo = void 0;
  261. if (columnIds.length) {
  262. columnInfo = {
  263. slotId: mappedSlot.getId(),
  264. columnIds: columnIds
  265. };
  266. }
  267. return columnInfo;
  268. });
  269. return _.filter(columnInfos, function (columnInfo) {
  270. return columnInfo;
  271. });
  272. },
  273. /*
  274. * Shows the toast for dropped columns. Takes a list of column metadata as argument.
  275. */
  276. _showToastForDroppedColumns: function _showToastForDroppedColumns(messageId, droppedColumns) {
  277. if (!droppedColumns || !droppedColumns.length) {
  278. return;
  279. }
  280. var columnLabels = droppedColumns.map(function (column) {
  281. return column.getLabel();
  282. }).join(', ').toLocaleString();
  283. var toastInfoText = StringResources.get(messageId, { columns: columnLabels });
  284. this._dashboardApi.showToast(toastInfoText, { type: 'info', preventDuplicates: true });
  285. },
  286. _getUsedMetadataColumns: function _getUsedMetadataColumns(visualization) {
  287. var columns = [];
  288. visualization.getSlots().getMappingInfoList().forEach(function (mapping) {
  289. if (mapping.slot) {
  290. columns.push(mapping.dataItem.getColumnId());
  291. }
  292. });
  293. return columns;
  294. },
  295. _recommendBindingsForLegacyMap: function _recommendBindingsForLegacyMap(visualization, visId, newColumnsIds) {
  296. var bKeepExistingMappings = true;
  297. if (!newColumnsIds) {
  298. //drop existing mapping if user is changing visualization
  299. bKeepExistingMappings = false;
  300. newColumnsIds = this._getUsedMetadataColumns(visualization);
  301. }
  302. var columns = newColumnsIds.map(function (columnId) {
  303. return visualization.getDataSource().getMetadataColumn(columnId);
  304. });
  305. return this._getVisDefinition(visId).then(function (visDefinition) {
  306. var legacyMapMappingManager = new this._LegacyMapMappingManager(visualization);
  307. var mappedColumns = legacyMapMappingManager.mapColumnsToSlots(visDefinition, columns, bKeepExistingMappings);
  308. if (legacyMapMappingManager.getUnmappedColumns()) {
  309. this._showToastForDroppedColumns('maxColumnsExceeded', legacyMapMappingManager.getUnmappedColumns());
  310. }
  311. return {
  312. slots: mappedColumns,
  313. visId: visDefinition.getId()
  314. };
  315. }.bind(this));
  316. },
  317. _recommendBindingsForList_DataPlayer: function _recommendBindingsForList_DataPlayer(visualization, visId, newColumnsIds) {
  318. //The mapping of slots for both vis types is identical as they both support only one slot and any data type
  319. return this._getVisDefinition(visId).then(function (visDefinition) {
  320. var metadataColumns = this._getUsedMetadataColumns(visualization);
  321. if (newColumnsIds) {
  322. metadataColumns = metadataColumns.concat(newColumnsIds);
  323. }
  324. // If the number of columns exceeded the max allowed for slot, either
  325. // a. in the case of change vis type - drop enough columns to meet the max no. of columns limit or
  326. // b. in the case of add columns - add enough columns to equal the max no. of columns limit and drop the rest
  327. var droppedColumns;
  328. var maxItem = visDefinition.getSlotList()[0].getProperty('maxItems');
  329. if (metadataColumns.length > maxItem) {
  330. droppedColumns = metadataColumns.splice(maxItem);
  331. }
  332. if (droppedColumns && droppedColumns.length > 0) {
  333. this._showToastForDroppedColumns('maxColumnsExceeded', droppedColumns.map(function (id) {
  334. return visualization.getDataSource().getMetadataColumn(id);
  335. }));
  336. }
  337. var recommendation = {
  338. slots: {},
  339. visId: visDefinition.getId()
  340. };
  341. recommendation.slots[visDefinition.getSlotList()[0].getId()] = metadataColumns;
  342. return recommendation;
  343. }.bind(this));
  344. },
  345. _isUnsupportedVisId: function _isUnsupportedVisId(visId) {
  346. return visId == undefined ? true : this._UnsupportedVisIds.indexOf(visId) !== -1;
  347. },
  348. _handleUnboundColumns: function _handleUnboundColumns(visualization, recommendation, slotDefinitions) {
  349. var unboundColumnIds = recommendation.unbound;
  350. if (!unboundColumnIds || !unboundColumnIds.length) {
  351. return recommendation;
  352. }
  353. var VisRecommenderBindingFallback = new this._VisRecommenderBindingFallback(visualization);
  354. recommendation = VisRecommenderBindingFallback.assignUnboundColumns(recommendation, slotDefinitions);
  355. //show toast for columns still unable to bind
  356. var label = recommendation.unbound.length > 1 ? 'unboundColumns' : 'unboundColumn';
  357. var dataSource = visualization.getDataSource();
  358. this._showToastForDroppedColumns(label, recommendation.unbound.map(function (columnId) {
  359. return dataSource.getMetadataColumn(columnId);
  360. }));
  361. return recommendation;
  362. },
  363. _getModule: function _getModule(visualization) {
  364. //should not cache module because it might be undefined when the viz has no mappings
  365. var module = void 0;
  366. var dataSource = visualization.getDataSource();
  367. if (dataSource) {
  368. module = this.moserDataSources.getModule(dataSource.getId());
  369. }
  370. return module;
  371. }
  372. });
  373. return DashboardSmartsVisRecommenderWrapper;
  374. });
  375. //# sourceMappingURL=DashboardSmartsVisRecommenderWrapper.js.map