'use strict'; /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2018 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restrictedashboardSpecSvcd by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ 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) { 'use strict'; /* * This class is a wrapper for the Smarts Visualization Recommender for dashboard. */ var DashboardSmartsVisRecommenderWrapper = BaseSmartsVisRecommenderWrapper.extend({ _UnsupportedVisIds: ['list', 'dataPlayer', 'com.ibm.vis.rave2polygonmap'], // not supported by SmartsVisRecommender _LegacyMapMappingManager: LegacyMapMappingManager, //for unit tests _VisRecommenderBindingFallback: VisRecommenderBindingFallback, // constructor init: function init(_ref) { var moserDataSources = _ref.moserDataSources, visDefinitions = _ref.visDefinitions, logger = _ref.logger, dashboardApi = _ref.dashboardApi, dataSources = _ref.dataSources; this._visDefinitions = visDefinitions; this._logger = logger; this._dashboardApi = dashboardApi; var ajaxSvc = dashboardApi.getGlassCoreSvc('.Ajax'); var glassOptions = {}; dashboardApi.prepareGlassOptions(glassOptions); DashboardSmartsVisRecommenderWrapper.inherited('init', this, [{ moserDataSources: moserDataSources, ajaxSvc: ajaxSvc, logger: logger, glassOptions: glassOptions, dashboardApi: dashboardApi, dataSources: dataSources }]); }, destroy: function destroy() { DashboardSmartsVisRecommenderWrapper.inherited('destroy', this, arguments); this.moserDataSources = null; this._visDefinitions = null; this._logger = null; this._dashboardApi = null; }, /** * Fetch the recommender information for the current chart * @return {object} payload from smarts server (including `label` attribute) */ getRecommendInfo: function getRecommendInfo(visualization) { var _this = this; var columns = this._getBoundColumnsInfo(visualization); this._replaceHiddenCustomGroupCols(columns, visualization); var options = this._getRequestOptions(visualization); var definition = visualization.getDefinition(); return DashboardSmartsVisRecommenderWrapper.inherited('getRecommendInfo', this, [{ columns: columns, visId: definition ? definition.getId() : null, requestOptions: options }]).then(function (chartInfo) { if (chartInfo.mandatorySlotsMissing) { return _this._buildChartInfoFromColumns(columns, options.module); } return chartInfo; }).catch(function (error) { if (error.message === _this.getUnsupportedVizErrCode()) { return _this._buildChartInfoFromColumns(columns, options.module); } else { return Promise.reject(error); } }); }, _replaceHiddenCustomGroupCols: function _replaceHiddenCustomGroupCols(columns, visualization) { var sourceId = visualization.getDataSource().getId(); var shaping = this._dashboardApi.getFeature('DataSources.moser'); if (shaping && shaping.isConsumerGroupColumn) { columns.forEach(function (columnEntry) { var columnIds = columnEntry.columnIds; columnIds.forEach(function (colId, i) { if (shaping.isConsumerGroupColumn(sourceId, colId)) { var customGroup = shaping.getCustomGroupData(sourceId, colId); if (customGroup && customGroup.basedOnMoserObjectId) { columnIds[i] = customGroup.basedOnMoserObjectId; } } }); }); } }, /* * This works as a local fallback to get smart title for smarts-unsupported visualization types (such as list, legacy map etc) * By design, we will use the concatenated column labels as the 'smarts title' */ _buildChartInfoFromColumns: function _buildChartInfoFromColumns(columns, module) { var aTitle = []; if (columns && columns.length > 0 && module) { columns.forEach(function (column) { column.columnIds.forEach(function (id) { module.getMetadataColumn(id) && aTitle.push(module.getMetadataColumn(id).getLabel()); }); }); } var joinedTitles = aTitle.join(StringResources.get('listSeparator') + ' '); //This could be augmented to create descriptions return Promise.resolve({ label: joinedTitles, title: joinedTitles }); }, /* * Given a visualization type, recommends the binding for the newly added columns (user added new columns to locked viz type) * @param {string} visId * @param {object} array of metadataColumn ids for the newly added columns * @returns {Object} */ recommendBindingsForNewColumns: function recommendBindingsForNewColumns(visualization, columnsToBind) { var _this2 = this; var visId = visualization.getDefinition().getId(); if (this._isUnsupportedVisId(visId)) { return this._recommendBindingsForUnsupportedVisId(visualization, visId, columnsToBind); } return new Promise(function (resolve) { var boundColumnsInfo = _this2._getBoundColumnsInfo(visualization); var requestOptions = _this2._getRequestOptions(visualization); resolve({ columnsToBind: columnsToBind, boundColumnsInfo: boundColumnsInfo, requestOptions: requestOptions, visId: visId }); }).then(this.recommendBindings.bind(this)).then(this._handleBindingRecommendationResponse.bind(this, visualization)).catch(this._handleSmartsError.bind(this)); }, /* * Recommend bindings (aka mappings) for the newly selected visualization (user chose to change viz type) based on columns in the current model. * @param {string} visId * @returns {Object} */ recommendBindingsForSelectedViz: function recommendBindingsForSelectedViz(visualization, visId) { if (this._isUnsupportedVisId(visId)) { return this._recommendBindingsForUnsupportedVisId(visualization, visId); } var requestParams = { visId: visId, requestOptions: this._getRequestOptions(visualization) }; var definition = visualization.getDefinition(); var originalVisId = definition ? definition.getId() : null; if (this._isUnsupportedVisId(originalVisId)) { requestParams.columnsToBind = Utils.getUnboundColumnsIds(visualization); } else { requestParams.originalVisId = originalVisId; requestParams.boundColumnsInfo = this._getBoundColumnsInfo(visualization); } return this.recommendBindings(requestParams).then(this._handleBindingRecommendationResponse.bind(this, visualization)).catch(this._handleSmartsError.bind(this)); }, /* * Returns the best alternate visualization recommendation for the columns in the current model plus * new columns added (if any). * @param {newColumns} - optional * @returns {Object} */ recommendBestAlternateVisualization: function recommendBestAlternateVisualization(visualization) { var _this3 = this; var newColumnsIds = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; return new Promise(function (resolve) { var columnIds = newColumnsIds.concat(Utils.getUnboundColumnsIds(visualization)); resolve(columnIds); }).then(function (columnIds) { return _this3.recommendAlternateVisualizations(columnIds, _this3._getRequestOptions(visualization)); }).then(function (recommendations) { return recommendations[0]; }).catch(this._handleSmartsError.bind(this)); }, /* * Assign the slots for visualizations that are not supported by Smarts visRecommender */ _recommendBindingsForUnsupportedVisId: function _recommendBindingsForUnsupportedVisId(visualization, visId, newColumnsIds) { switch (visId) { case 'com.ibm.vis.rave2polygonmap': { return this._recommendBindingsForLegacyMap(visualization, visId, newColumnsIds); } case 'dataPlayer': case 'list': { return this._recommendBindingsForList_DataPlayer(visualization, visId, newColumnsIds); } } }, _handleSmartsError: function _handleSmartsError(e) { this._logger.warn('An error occurred in the smart recommender', e); throw e; }, _handleBindingRecommendationResponse: function _handleBindingRecommendationResponse(visualization, recommendations) { var _this4 = this; /* * Recommender may return returns > 1 binding recommendations and the recommendation may contain * unsupported slots or recommendations that do not render in Dashboard/Explore due to required slots not being mapped * This function filters out the recommendations that contains unsupported slots and pick the best recommendation based on max * required slots mapped - this will reduce the chance that the visualization is not rendered. */ //This is a binding request so the visId for all the recommendations are the same, so just use the first one. return this._getVisDefinition(recommendations[0].visId).then(function (visDefinition) { //filter out the hidden slots so we dont recommend them var slotDefinitions = visDefinition.getSlotList().filter(function (slot) { return !slot.getProperty('hidden'); }); var allSlotIds = slotDefinitions.map(function (dataSlot) { return dataSlot.getId(); }); var allSlotsAreSupported = function allSlotsAreSupported(recommendation) { return Object.keys(recommendation.slots).filter(function (slotId) { return allSlotIds.indexOf(slotId) === -1; }).length === 0; }; var requiredSlotIds = visDefinition.getSlotList().filter(function (dataSlot) { return !dataSlot.getProperty('optional'); }).map(function (dataSlot) { return dataSlot.getId(); }); // returns the recommendation that has the max required slots for the viz var maxRequiredSlotsMatched = function maxRequiredSlotsMatched(accumulator, currentRec) { var maxRequiredSlots = function maxRequiredSlots(recommendation) { return Object.keys(recommendation.slots).filter(function (slotId) { return requiredSlotIds.indexOf(slotId) !== -1; }).length; }; if (maxRequiredSlots(currentRec) > maxRequiredSlots(accumulator)) { return currentRec; } return accumulator; }; var recommendation = recommendations.filter(allSlotsAreSupported).reduce(maxRequiredSlotsMatched); //attempt to manually assign bindings for columns that smarts was unable to bind and show a toast for any that cannot be bound return _this4._handleUnboundColumns(visualization, recommendation, slotDefinitions); }); }, _getRequestOptions: function _getRequestOptions(visualization) { var module = this._getModule(visualization); return { assetId: module.getAssetId(), sourceType: module.getSourceType(), module: module }; }, _getVisDefinition: function _getVisDefinition(visId) { return Promise.resolve(this._visDefinitions.getById(visId)); }, //Return an array of column info with column and (SmartsVisRecommender) slot ids _getBoundColumnsInfo: function _getBoundColumnsInfo(visualization) { var mappedSlots = visualization.getSlots().getMappedSlotList(); if (!mappedSlots || !mappedSlots.length) { return; } // ignore hidden slots which are not considered as part of mappings // e.g. 'rank' slot of grid, 'size' slot of scatter var columnInfos = mappedSlots.filter(function (slot) { return slot && !slot.getDefinition().isHidden(); }).map(function (mappedSlot) { var columnIds = Utils.filterInvalidDataItems(mappedSlot.getDataItemList()).map(function (dataItem) { return dataItem.getColumnId(); }); var columnInfo = void 0; if (columnIds.length) { columnInfo = { slotId: mappedSlot.getId(), columnIds: columnIds }; } return columnInfo; }); return _.filter(columnInfos, function (columnInfo) { return columnInfo; }); }, /* * Shows the toast for dropped columns. Takes a list of column metadata as argument. */ _showToastForDroppedColumns: function _showToastForDroppedColumns(messageId, droppedColumns) { if (!droppedColumns || !droppedColumns.length) { return; } var columnLabels = droppedColumns.map(function (column) { return column.getLabel(); }).join(', ').toLocaleString(); var toastInfoText = StringResources.get(messageId, { columns: columnLabels }); this._dashboardApi.showToast(toastInfoText, { type: 'info', preventDuplicates: true }); }, _getUsedMetadataColumns: function _getUsedMetadataColumns(visualization) { var columns = []; visualization.getSlots().getMappingInfoList().forEach(function (mapping) { if (mapping.slot) { columns.push(mapping.dataItem.getColumnId()); } }); return columns; }, _recommendBindingsForLegacyMap: function _recommendBindingsForLegacyMap(visualization, visId, newColumnsIds) { var bKeepExistingMappings = true; if (!newColumnsIds) { //drop existing mapping if user is changing visualization bKeepExistingMappings = false; newColumnsIds = this._getUsedMetadataColumns(visualization); } var columns = newColumnsIds.map(function (columnId) { return visualization.getDataSource().getMetadataColumn(columnId); }); return this._getVisDefinition(visId).then(function (visDefinition) { var legacyMapMappingManager = new this._LegacyMapMappingManager(visualization); var mappedColumns = legacyMapMappingManager.mapColumnsToSlots(visDefinition, columns, bKeepExistingMappings); if (legacyMapMappingManager.getUnmappedColumns()) { this._showToastForDroppedColumns('maxColumnsExceeded', legacyMapMappingManager.getUnmappedColumns()); } return { slots: mappedColumns, visId: visDefinition.getId() }; }.bind(this)); }, _recommendBindingsForList_DataPlayer: function _recommendBindingsForList_DataPlayer(visualization, visId, newColumnsIds) { //The mapping of slots for both vis types is identical as they both support only one slot and any data type return this._getVisDefinition(visId).then(function (visDefinition) { var metadataColumns = this._getUsedMetadataColumns(visualization); if (newColumnsIds) { metadataColumns = metadataColumns.concat(newColumnsIds); } // If the number of columns exceeded the max allowed for slot, either // a. in the case of change vis type - drop enough columns to meet the max no. of columns limit or // b. in the case of add columns - add enough columns to equal the max no. of columns limit and drop the rest var droppedColumns; var maxItem = visDefinition.getSlotList()[0].getProperty('maxItems'); if (metadataColumns.length > maxItem) { droppedColumns = metadataColumns.splice(maxItem); } if (droppedColumns && droppedColumns.length > 0) { this._showToastForDroppedColumns('maxColumnsExceeded', droppedColumns.map(function (id) { return visualization.getDataSource().getMetadataColumn(id); })); } var recommendation = { slots: {}, visId: visDefinition.getId() }; recommendation.slots[visDefinition.getSlotList()[0].getId()] = metadataColumns; return recommendation; }.bind(this)); }, _isUnsupportedVisId: function _isUnsupportedVisId(visId) { return visId == undefined ? true : this._UnsupportedVisIds.indexOf(visId) !== -1; }, _handleUnboundColumns: function _handleUnboundColumns(visualization, recommendation, slotDefinitions) { var unboundColumnIds = recommendation.unbound; if (!unboundColumnIds || !unboundColumnIds.length) { return recommendation; } var VisRecommenderBindingFallback = new this._VisRecommenderBindingFallback(visualization); recommendation = VisRecommenderBindingFallback.assignUnboundColumns(recommendation, slotDefinitions); //show toast for columns still unable to bind var label = recommendation.unbound.length > 1 ? 'unboundColumns' : 'unboundColumn'; var dataSource = visualization.getDataSource(); this._showToastForDroppedColumns(label, recommendation.unbound.map(function (columnId) { return dataSource.getMetadataColumn(columnId); })); return recommendation; }, _getModule: function _getModule(visualization) { //should not cache module because it might be undefined when the viz has no mappings var module = void 0; var dataSource = visualization.getDataSource(); if (dataSource) { module = this.moserDataSources.getModule(dataSource.getId()); } return module; } }); return DashboardSmartsVisRecommenderWrapper; }); //# sourceMappingURL=DashboardSmartsVisRecommenderWrapper.js.map