'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2017, 2020 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['underscore', '../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../dataSources/ModuleUtils', '../../widgets/livewidget/query/QueryFilterSpec', '../../widgets/livewidget/nls/StringResources', './VIPRUtils'], function (_, Class, ModuleUtils, QueryFilterSpec, stringResources, VIPRUtils) { 'use strict'; // IDataRequestHandler implementation to provide secondary query results to VIPR var VIPRHandler = Class.extend({ CATALYST_OP: { KEYDRIVER: 'GetKeyDrivers', DECISIONTREE: 'GetDecisionTree' }, // view warning ids GEO_AMBIGUOUS_ID: 'geo_ambiguous', GEO_UNMATCHED_ID: 'geo_unmatched', PREDICT_ERROR_ID: 'predict_error', PREDICT_WARNING_ID: 'predict_warning', TILED_MAP_ATLAS: 'tiledMap_atlas', TYPE_ATLAS: 'atlas', _lastRequestData: {}, init: function init(options) { this.logger = options.logger; this.visAPI = options.visAPI; this.queryService = this.visAPI.getQueryManager().queryService; this.visView = options.visView; this.dashboardApi = this.visAPI.ownerWidget.getDashboardApi(); /*Each request could specify a request complete callback*/ this._callbacks = { 'Atlas': this._geoQueryCallback.bind(this), 'MapPolygons': this._geoQueryCallback.bind(this), 'Catalyst': this._fastPredictCallback.bind(this) }; this._customData = null; }, resetCustomData: function resetCustomData() { this._customData = null; }, _geoQueryCallback: function _geoQueryCallback(visView, data) { if (visView && visView.infoIndicator && data) { var candidates = [{ id: this.GEO_AMBIGUOUS_ID, property: 'ambiguous', resourceId: 'geomapAmbiguousLocations' }, { id: this.GEO_UNMATCHED_ID, property: 'unmatched', resourceId: 'geomapUnrecognizedLocations' }]; visView.infoIndicator.addInfo(_.map(candidates, function (e) { return { id: e.id, label: stringResources.get(e.resourceId), items: _.map(data[e.property], function (s) { return { id: '_' + s.replace(/\s/g, '_'), label: s }; }) }; })); } }, _addErrorsAndWarningsForCandidates: function _addErrorsAndWarningsForCandidates(visView, candidates, predictStatus) { _.each(candidates, function (candidate) { var properties = predictStatus[candidate.property]; if (properties && properties.length) { visView.infoIndicator.addInfo([{ id: candidate.id, label: stringResources.get(candidate.resourceId), items: _.map(properties, function (prop) { return { id: prop.id, label: prop.caption }; }) }]); } }); }, /** * FPD requests may result in messages being added that we want * displayed in the insights indicator dialog. We should check * for those here and notify needed managers to the changes. */ _addFPDInsightMessages: function _addFPDInsightMessages(predictStatus) { // Only log messages if the bundle is configured to accept it. if (VIPRUtils.doesConfigPropertyMatchExpected(this.visAPI.getVisId(), 'shouldLogFPDInsightMessages', true)) { var smartsAnnotationManager = this.visAPI.getSmartAnnotationsManger(); // no point doing anything if we have no manager to report to if (smartsAnnotationManager) { var messages = null; // Default to no messages if (predictStatus && predictStatus.messages && predictStatus.messages.length > 0) { messages = predictStatus.messages; } smartsAnnotationManager.addFPDInsightMessages(messages); this.visView.updateSmartAnnotationsIndicator(); } } }, _fastPredictCallback: function _fastPredictCallback(visView, data) { if (visView && visView.infoIndicator && data && data.predictStatus) { // check for FPD call back messages and report them if needed this._addFPDInsightMessages(data.predictStatus); var predictStatus = data.predictStatus; var candidates = [{ id: this.PREDICT_ERROR_ID, property: 'errors', resourceId: 'predict_errors' }, { id: this.PREDICT_WARNING_ID, property: 'warnings', resourceId: 'predict_warnings' }]; this._addErrorsAndWarningsForCandidates(visView, candidates, predictStatus); } }, // Implement IDataRequestHandler request: function request(_request) { switch (_request.name) { case 'Schematics': this._handleSchematicRequest(_request); break; case 'Atlas': case 'MapPolygons': this._handleGeoRequest(_request); break; case 'Catalyst': this._handleKeyDriversRequest(_request); break; default: this._requestFail(_request); } }, /** * Log a notification from VIPR * @param {String} level (Severity) The severity of the notification, e.g. "info", "warn" or "error". * @param {String} message The message of the notification. * @param {Object} [payload] Optional: payload of the notification, for truncations when data is clipped */ _logNotification: function _logNotification(level, message, payload) { var _this = this; switch (level) { case 'warn': { this.logger.warn(message); if (payload && payload.truncations) { payload.truncations.forEach(function (truncation) { _this.logger.warn(truncation.toString()); }); } break; } case 'error': { this.logger.error(message); if (payload && payload.truncations) { payload.truncations.forEach(function (truncation) { _this.logger.error(truncation.toString()); }); } break; } } }, /** * Display a notification from VIPR * @param {String} level (Severity) The severity of the notification, e.g. "info", "warn" or "error". * @param {String} message The message of the notification. * @param {Object} [payload] Optional: payload of the notification, for truncations when data is clipped */ _addNotifyMessagesToInfoIndicator: function _addNotifyMessagesToInfoIndicator(level, message, payload, id, type) { var _this2 = this; // If there is a message lets add it to our info indicator. if (this.visView && this.visView.infoIndicator && message) { var candidates = [{ id: 'visualization_notifications', property: 'visualizationnotifications', resourceId: 'visualizationNotifications' }]; var items = []; items.push(this._getMessageLabel(message, false, id, type)); if (payload && payload.truncations) { payload.truncations.forEach(function (truncation) { (function (message) { items.push(this._getMessageLabel(message, true /* isSubMessage */)); }).bind(_this2)(truncation.toString()); }); } this.visView.infoIndicator.addInfo(_.map(candidates, function (e) { return { id: e.id, label: stringResources.get(e.resourceId), items: items }; })); } }, /** * @param {String} messageString - message to add * @param {Boolean} isSubMessage - is this a sub message of a main message (more clipping info for example) * @returns {Object} in form needed for the info indicator view * */ _getMessageLabel: function _getMessageLabel(messageString, isSubMessage, id, type) { return { id: id || '_' + messageString.replace(/\s/g, '_'), type: type, label: messageString, isSubMessage: isSubMessage ? isSubMessage : false }; }, _isNotificationAGeoWarning: function _isNotificationAGeoWarning(level, id) { return level === 'warn' && id === this.TILED_MAP_ATLAS; }, /** * Handle a notification from VIPR * @param {String} level (Severity) The severity of the notification, e.g. "info", "warn" or "error". * @param {String} message The message of the notification. * @param {Object} [payload] Optional: payload of the notification, for truncations when data is clipped * @param {String} id - id of the message being returned */ notify: function notify(_notification) { this._logNotification(_notification.level, _notification.localizedMessage, _notification.payload); /* Geo messages are a little different then others. We currently listen to * the responses coming back from the geo query. We also listen to the notifications * being sent back from Vida. They both state the same thing so we see them both in the * Info Indicator dialog. Normally we'd ignore the geo response and listen only * to Vida, but Vida returns its warnings as a formatted string such as * 'Ambigous locations: ["Canada"]' which means we'd have to parse it interpret * it to add it to the info indicator in the way we want. The geo response * provides it in a key value pair that makes this very easy. So, we will * listen to the geo response, add it to the info indicator, but in this * notify message we will only log it and not add it to the info indicator. */ if (_notification.type === 'clear') { this._clearIndicatorWarning(_notification); } else if (!this._isNotificationAGeoWarning(_notification.level, _notification.id)) { this._addNotifyMessagesToInfoIndicator(_notification.level, _notification.localizedMessage, _notification.payload, _notification.id, _notification.type); } }, _clearIndicatorWarning: function _clearIndicatorWarning(notification) { var _this3 = this; if (notification.payload && notification.payload.length) { notification.payload.forEach(function (item) { if (item.id === _this3.TILED_MAP_ATLAS && item.type === _this3.TYPE_ATLAS) { _this3.visView.infoIndicator.clearMessagesWithIds([_this3.GEO_AMBIGUOUS_ID, _this3.GEO_UNMATCHED_ID]); } else { _this3.visView.infoIndicator.clearMessagesByIdAndSubtype(item); } }); } }, _setDefaultSelections: function _setDefaultSelections() { var enableSelection = this.dashboardApi.getConfiguration('enableCustomDataSelection') || VIPRUtils.supportsCustomDataSelection(this.visAPI.getVisId()); if (enableSelection) { var customDataSelections = this.visAPI.getDecoratedCustomData('selected'); var renderingNewData = !!(this.visView && this.visView.getRenderingNewData()); // Default to the first customData to be selected if (renderingNewData && customDataSelections.length === 0 && this._customData.length > 0) { var defaultSelection = this._customData[0]; var defaultSelections = []; var defaultSelectionIds = []; // For comet: default first value should be grouped with others if they have the same valueFloored if (defaultSelection.hasOwnProperty('valueFloored')) { defaultSelections = this._customData.filter(function (data) { return data.valueFloored === defaultSelection.valueFloored; }); defaultSelectionIds = defaultSelections.map(function (data) { return { id: data.id, value: true }; }); } else { defaultSelections.push(defaultSelection); defaultSelectionIds.push({ id: defaultSelection.id, value: true }); } this.visAPI.setCustomDataDecoration('selected', defaultSelectionIds, { silent: true }); customDataSelections.push.apply(customDataSelections, defaultSelections); } customDataSelections.forEach(function (item) { item.setDecoration && item.setDecoration('selected', true); }.bind(this)); } }, setCustomData: function setCustomData(type, customData) { if (!this._customData) { this._customData = customData; this._setDefaultSelections(); } }, getCustomData: function getCustomData() { return this._customData; }, _invokeCallback: function _invokeCallback(name, data) { var fnCallback = this._callbacks[name]; if (fnCallback) { fnCallback(this.visView, data); } }, _requestComplete: function _requestComplete(_request, data) { var shouldCache = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (shouldCache) { this.setLastRequestData(_request.name, data); } if (_request.name === 'Catalyst') { //for catalyst we always cache the raw data, then filter it on demand //so that predict data can be shared by decision tree and other driver analysis var operation = this._getCatalystOperation(_request); var methods = this._getFastPatternDetectionMethods(operation); data = this._filterPredictResults(data, methods); } this._invokeCallback(_request.name, data); _request.complete(data); }, _requestFail: function _requestFail(_request, data) { var shouldCache = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (shouldCache) { this.setLastRequestData(_request.name, null); } this._invokeCallback(_request.name, data); _request.fail(data); }, /** * Last request data setter * * @param {string} key - key to identify the request * @param {object} data - data to store */ setLastRequestData: function setLastRequestData(key, data) { this._lastRequestData[key] = data; }, /** * Last request data getter * * @param {string} key - key to identify the request * * @return {object} previously stored data */ getLastRequestData: function getLastRequestData(key) { return this._lastRequestData[key]; }, /** * Make the rest API call to query for the SVG associated with a schematic * @param {json object} payload. payload are in the following format: * payload = { method: "get", library: , type: "meta" } * payload = { method: "get", library: , name: } * @returns {json object} for first payload it return the content of bi/v1/visualizations/[id]/content/meta.json in json format. * for second payload it returns content svg in json format. */ _handleSchematicRequest: function _handleSchematicRequest(_request) { var _this4 = this; var getSchematicContent = this.visView.visualization.getDefinition().getProperty('getSchematicContent'); if (getSchematicContent && _request.payload) { return getSchematicContent(_request.payload, this.dashboardApi).then(function (data) { _this4._requestComplete(_request, data); }, function (err) { _this4._requestFail(_request, err); }); } else { this._requestFail(_request, new Error(stringResources.get('schematicServiceRequestError'))); } }, _getGeoEndpoint: function _getGeoEndpoint() { var endpoint = this.dashboardApi.getConfiguration('geoService'); // force to use the default endpoint for "CA" return endpoint === 'CA' ? null : endpoint; }, _handleGeoRequest: function _handleGeoRequest(_request) { this.visView.infoIndicator.clearMessagesWithIds([this.GEO_AMBIGUOUS_ID, this.GEO_UNMATCHED_ID]); this.queryService.runGeoQuery(_request.payload, this._getGeoEndpoint()).then(function (data) { if (_request.payload.useMapBoxPB) { Object.defineProperty(data, 'results', Object.getOwnPropertyDescriptor(data, 'mapboxData')); } this._requestComplete(_request, data); }.bind(this), function (err) { this._requestFail(_request, err); }.bind(this)); }, _getCatalystOperation: function _getCatalystOperation(_request) { return _request && _request.payload && _request.payload.catalystOperation; }, _getFastPatternDetectionMethods: function _getFastPatternDetectionMethods(op) { return op === this.CATALYST_OP.DECISIONTREE ? ['tree'] : ['drivers', 'tree']; }, /** * If the request errored out, then we want to add the error * to the view info indicator and not call predict. This means * we need to return an empty result set to Vida so they don't error out * and can render the empty FPD base charts. * @param {Object} _request - original vida request * @param {Object} errorDesc - description of the current error state * @returns {Object} empty result and predict status. */ _handleErrorPossibleKeyDriverRequest: function _handleErrorPossibleKeyDriverRequest(_request, errorDesc) { var visView = this.visAPI.ownerWidget.getCurrentVis(); // If we have a vis view add an error to the info indicator. if (visView && visView.infoIndicator) { var msg = errorDesc.error.caption; var catId = errorDesc.error && errorDesc.error.categoryId ? errorDesc.error.categoryId : 'predict_error'; visView.infoIndicator.addInfo([{ id: catId, label: stringResources.get(catId), items: [{ id: '_' + msg.replace(/\s/g, '_'), label: msg }] }]); } // We need to return an empty result so Vida isn't left hanging. var data = { result: [], predictStatus: {} }; this._requestComplete(_request, data); }, /** * The cataclyst service does not behave well when sending queries with empty * possible drivers. So, we won't send the query (which saves time and resources) * anyway. Instead we create our own dummy response with an empty result and * complete the request. Note, we do not need to add a warning here as Vida * will notify us with a message for the user that the target has no key drivers. * @param {Object} _request - the original request from Vida. */ _handleEmptyPossibleKeyDriverRequest: function _handleEmptyPossibleKeyDriverRequest(_request) { var data = { result: [], predictStatus: {} }; this._requestComplete(_request, data); }, /** * If the FPD request fails outright (say with a 500) it will return a response * with errors we can add to the info indicator. We should log those before * failing the initial request. * @param {Object} _request - the initial request * @param {Object} errorData - holds, among other props, an array of errors * TODO: We now have a number of ways of dealing with adding information to the * info indicator depending on what endpoint we are hitting, if there are network * failures, if the query fails, etc... We need to work to simplify this and add * consistency with the endpoints and what they return. */ _fpdRequestFailed: function _fpdRequestFailed(_request, errorData) { // Deal with adding the failure messages to the info indicator and logging them. if (this.visView && this.visView.infoIndicator) { var info = { label: stringResources.get('predict_errors'), id: this.PREDICT_ERROR_ID, items: [] }; if (errorData && errorData.errors && errorData.errors.length) { errorData.errors.forEach(function (errorDesc) { info.items.push({ id: errorDesc.code, label: errorDesc.message }); }); } else { // use a generic server message if nothing is supplied info.items.push({ id: errorData && errorData.code || 500, label: stringResources.get('catalyst_service_error') }); } this.visView.infoIndicator.addInfo([info]); } // Notify the caller the request failed. this._requestFail(_request, new Error(stringResources.get('catalyst_service_internal_server_error'))); }, _handleKeyDriversRequest: function _handleKeyDriversRequest(_request) { var _this5 = this; this.visView.infoIndicator.clearMessagesWithIds([this.PREDICT_ERROR_ID, this.PREDICT_WARNING_ID]); var targetId = this._getTargetId(_request); var operation = this._getCatalystOperation(_request); var methods = this._getFastPatternDetectionMethods(operation); var possibleKeyDriversDesc = this.visAPI.getEnabledPossibleKeyDrivers(targetId); /* * There are two possible scenarios when getting the possible key drivers * 1) There is an error condition. * b) There are valid possible key drivers */ // Case 1: error state. if (possibleKeyDriversDesc.errorState && possibleKeyDriversDesc.errorState.inErrorState) { this._handleErrorPossibleKeyDriverRequest(_request, possibleKeyDriversDesc.errorState); } else { // Case b) which includes empty list of possible key drivers. var possibleKeyDrivers = possibleKeyDriversDesc.possibleKeyDrivers; try { this.visAPI.executeFastPatternDetectionRequest(targetId, possibleKeyDrivers, methods).then(function (data) { _this5._requestComplete(_request, data); }, function (e) { _this5._fpdRequestFailed(_request, e && e.data); }); } catch (err) { this.logger.error(err); } } }, _getValidPredictResultTypes: function _getValidPredictResultTypes(methods) { var validTypes = []; _.each(methods, function (method) { switch (method) { case 'tree': validTypes.push('TREE'); break; case 'oneway': validTypes.push('ONE_WAY'); break; case 'twoway': validTypes.push('TWO_WAY'); break; case 'drivers': validTypes.push('ONE_WAY'); validTypes.push('TWO_WAY'); break; default: } }); return validTypes; }, _filterPredictResults: function _filterPredictResults(data, methods) { var validTypes = this._getValidPredictResultTypes(methods); if (validTypes && validTypes.length > 0) { data.results = _.filter(data.results, function (result) { return validTypes.indexOf(result.type) > -1; }); } return data; }, _getTargetId: function _getTargetId(_request) { var wa_attributeUniqueName; var targetId = _request.payload.catalystPayload.target; // TODO: massage the targetId to the Predict expected format for now // This will be removed once the bugs have been resolved between VIPR & Predict var targetMetaDataColumns = this.visAPI.getModule().getMetadataColumns(function (metaDataColumn) { return metaDataColumn.getId() === targetId; }); if (targetMetaDataColumns.length) { wa_attributeUniqueName = ModuleUtils.getColumnPropertyValue(targetMetaDataColumns[0], 'wa_attributeUniqueName'); } return wa_attributeUniqueName || targetId; }, _addLocalFiltersToRequest: function _addLocalFiltersToRequest(params) { var aFilters = this._getWidgetLocalFilters(); if (aFilters) { params.sessionShapingChange = true; params.localFilters = this._normalizeFilters(aFilters); } }, /** * Get localFilters of the current widget. */ _getWidgetLocalFilters: function _getWidgetLocalFilters() { var localFiltersDefinition = this.visAPI.getLocalFilters(); var queryFilterSpec = new QueryFilterSpec(); queryFilterSpec.addFiltersToSpec(localFiltersDefinition); if (queryFilterSpec.hasFilterSpec()) { return queryFilterSpec.getFilterSpec(); } return null; }, _normalizeFilters: function _normalizeFilters(aFilters) { var aFiltersStrings = []; _.each(aFilters, function (filter) { // Sort the filter values alphabetically to make sure prediction service will have the same // cache key if the filter content is the same but the order of filter values are different // or the order of attributes in the filter expression are different. filter = this._doSort(filter); aFiltersStrings.push(JSON.stringify(filter)); }.bind(this)); aFiltersStrings.sort(); var aSortedFilters = []; _.each(aFiltersStrings, function (filterStr) { aSortedFilters.push(JSON.parse(filterStr)); }); return aSortedFilters; }, _doSort: function _doSort(sortObj) { var sortedArray = []; var sortedObj; var aKeys = _.keys(sortObj); if (aKeys && aKeys.length > 1) { aKeys.sort(); } for (var i = 0; i < aKeys.length; i++) { var key = aKeys[i]; if (key === 'values') { // If the key is values, will sort an array of value Strings in alphabetically order. sortedObj = sortObj.values.sort(); } else if (_typeof(sortObj[key]) === 'object') { sortedObj = this._doSort(sortObj[key]); } else { sortedObj = sortObj[key]; } var obj = {}; obj[key] = sortedObj; sortedArray.push(obj); } return this._toSortedObject(sortedArray); }, _toSortedObject: function _toSortedObject(sortedArray) { return _.extend.apply(null, sortedArray); } }); return VIPRHandler; }); //# sourceMappingURL=VIPRDataRequestHandler.js.map