'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; }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2017, 2021 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['ca-modeller/shaping', 'bi/moser/moser.min', 'underscore', '../../nls/StringResources', '../../metadata/MetadataColumn', '../../utils/DashboardSpecUtil', '../../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', '../../../lib/@waca/core-client/js/core-client/ui/dialogs/ConfirmationDialog', '../../../apiHelpers/SlotAPIHelper'], function (Shaping, MoserJS, _, StringResources, MetadataColumn, DashboardSpecUtil, BrowserUtils, ConfirmationDialog, SlotAPIHelper) { 'use strict'; var MAP_SOURCETYPE_TO_USESPECTYPE = { uploadedFile: MoserJS.default.UseSpecType.FILE, 'package': MoserJS.default.UseSpecType.PACKAGE, dataSet2: MoserJS.default.UseSpecType.DATASET, module: MoserJS.default.UseSpecType.MODULE, data_asset: MoserJS.default.UseSpecType.WA_DATA_ASSET, url: MoserJS.default.UseSpecType.URL, report: MoserJS.default.UseSpecType.REPORT, socialMediaProject: MoserJS.default.UseSpecType.SOCIAL_MEDIA }; var SOURCE_TYPES_TO_UPDATE = ['dataset2', 'uploadedFile', 'module']; var UNWANTED_MOSER_OBJECT_TYPES_FOR_METADATACOLUMNS = [MoserJS.default.MoserObjectTypes.ITEM_TYPE, MoserJS.default.MoserObjectTypes.QUERY_SUBJECT, MoserJS.default.MoserObjectTypes.FOLDER, MoserJS.default.MoserObjectTypes.DEF_LINK_TYPE]; var ShapingModelManager = function () { function ShapingModelManager(options) { _classCallCheck(this, ShapingModelManager); this.dashboardApi = options.dashboardApi; this.sourceModel = options.sourceModel; this.logger = this.dashboardApi.getGlassCoreSvc('.Logger'); this.queryService = this.dashboardApi.getFeature('QueryService'); this.dashboardApi.on('mode:change', this.onModeChange, this); if (options.sourceModel) { this.shapingModel = options.sourceModel.get('shaping'); this.shapingModel.setShapingModelManager(this); } this.sourceModelManager = options.sourceModelManager; this.metadataColumnCache = {}; // Used to track what kind of module we're dealing with this._embeddedModule = true; this._queryTypeParam = 'module'; this._loadedMetadataColumns = {}; } ShapingModelManager.prototype.destroy = function destroy() { this._shapingHelper.destroy(); this._shapingHelper = null; this.module = null; this.tempModuleLoaded = null; }; ShapingModelManager.prototype.isTemporaryModule = function isTemporaryModule() { return this._embeddedModule; }; /** * Changing dashboard modes authoring to consume or consume to authoring */ ShapingModelManager.prototype.onModeChange = function onModeChange(payload) { if (payload && payload.authoring) { return this.ensureTemporaryModule(); } return Promise.resolve(this.module); }; /** * Ensures we are working against a temporary module */ ShapingModelManager.prototype.ensureTemporaryModule = function ensureTemporaryModule() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (this.isTemporaryModule()) { return Promise.resolve(this.module); } else { return this._loadModule(undefined, options).then(this._setModule.bind(this)).then(this.whenTemporaryModuleReady.bind(this)).catch(this._handleLoadError.bind(this, false)); } }; ShapingModelManager.prototype.addWADerivedAttribute = function addWADerivedAttribute(derivedAttribute) { this._initializeFormulaeConverter(); if (this.formulaeConverter && this.attributeProcessor) { var qs = this.module.getQuerySubject()[0]; var v5Exp = this.formulaeConverter.formulaeToV5Expression(derivedAttribute.formulae, MoserJS.default.ExpressionType.CALCULATION); var factory = MoserJS.default.createObjectFactory(); var qi = factory.createQueryItem(); qi.setFacetDefinition(factory.createFacetType()); MoserJS.default.ModuleUtils.generateObjectId(qs, qi); var prop = factory.createPropertyType(); prop.setName('wa_attributeUniqueName'); prop.setValue(derivedAttribute.uniqueName); qi.addProperty(prop); // Until MUI calls validate when adding a query item //qi.setDatatype('NVARCHAR(100)'); qi.setLabel(derivedAttribute.caption); qi.setExpression(v5Exp); this.attributeProcessor.execute(qi, derivedAttribute.formulae[0]); // Until we have the latest MUI if (this._shapingHelper.expressionAddToModuleWithValidation) { this._shapingHelper.expressionAddToModuleWithValidation(qi, qs); } // big hack - until MUI marks the module as dirty after adding a query item //this._shapingHelper.context.store.sessionSave.dirtyTracker._dirtyPointer++; } return Promise.resolve(); }; ShapingModelManager.prototype._initializeFormulaeConverter = function _initializeFormulaeConverter() { if (!this.formulaeConverter && this.module) { if (MoserJS.default.FormulaeV5Converter) { this.formulaeConverter = new MoserJS.default.FormulaeV5Converter(); this.formulaeConverter.initWithModule(this.module); } if (MoserJS.default.AttributeProcessor) { this.attributeProcessor = new MoserJS.default.AttributeProcessor(); this.attributeProcessor.initWithModule(this.module); } } }; ShapingModelManager.prototype._setModule = function _setModule(module) { if (this.module) { this.clearCache(); this.module.removeListener(this.moduleEventListener.bind(this)); } this.module = module; module.addListener(this.moduleEventListener.bind(this)); return module; }; /** * Creates a shapingHelper and moserJS object. Also makes sure the metadata is loaded * @param option module id that can be used to load the module if it already exist in the temporary session * @return {Promise} Promise that will be resolve when the module is finished loading */ ShapingModelManager.prototype.load = function load(moduleId, showErrorToast) { if (this._shapingHelper) { return Promise.resolve(); } return this._createShapingHelper().then(this._loadModule.bind(this, moduleId)).then(this._setModule.bind(this)).catch(this._handleLoadError.bind(this, showErrorToast)); }; ShapingModelManager.prototype._createShapingHelper = function _createShapingHelper() { var _this = this; return this.dashboardApi.getGlassSvc('.DataConnectionServiceFactory').then(function (dataConnectionServiceFactory) { return dataConnectionServiceFactory.getDataConnectionResolver({ onAmbiguousConnectionResolved: function onAmbiguousConnectionResolved() { _this.dashboardApi.triggerDashboardEvent('ambiguousConnection:resolved', { sourceId: _this.sourceModel.id }); } }); }).then(function (dataConnectionService) { var DndManager = _this.dashboardApi.getFeature('DashboardDnd.internal'); var enableNavigationGroupAction = !(_this.sourceModel.getIsOlapPackage() || _this.sourceModelManager.getType() === 'data_asset'); var enableCustomGroups = !_this.dashboardApi.getGlassCoreSvc('.FeatureChecker').checkValue('dashboard', 'customGroups', 'disabled'); var enableOlapMember = _this.dashboardApi.getGlassCoreSvc('.FeatureChecker').checkValue('dashboard', 'showMembers', 'enabled'); var options = {}; _this.dashboardApi.prepareGlassOptions(options); _this._shapingHelper = new Shaping.Shaping(options.glassContext, { glassDnD: DndManager, undoCallback: _this.addToUndoStack.bind(_this), confirmations: { removeFromModule: _this._removeItemsFromModule.bind(_this) }, featureConfig: { filterExpressionCreateAction: true, filterExpressionEditAction: true, filterExpressionRemoveAction: true, calculationAction: true, includeImportedObjects: false, removeAction: true, expressionEditorEditAction: true, expressionEditorAction: true, filterAction: true, navigationGroupAction: enableNavigationGroupAction, sortAction: true, filterIndividualValues: false, showFullPackage: true, showMembers: enableOlapMember || !!_this.dashboardApi.getAppConfig('showMembers'), formatAction: true, useValidationDialog: true, scrollableTrees: BrowserUtils.isIPad(), showModuleTreeNode: false, customGroupCreateAction: enableCustomGroups, customGroupReplaceAction: enableCustomGroups }, connectionResolver: dataConnectionService }); _this._shapingHelper.context.shapingHelper = _this._shapingHelper; //Add the shapingHelper to the context so that it can be accessed by context menu operations. _this.shapingModel.setShapingHelper(_this._shapingHelper); }).catch(function (error) { if (_this.logger) { _this.logger.error(error, _this); } }); }; // Callback function to resolve either true or false so MUI will contiue or cancel the remove action. ShapingModelManager.prototype._removeItemsFromModule = function _removeItemsFromModule() { return new Promise(function (resolve) { var oConfirmationDialog = new ConfirmationDialog('warning', StringResources.get('delete_confirm'), StringResources.get('calculation_navigation_in_use')); oConfirmationDialog.confirm(function () { resolve(true); }, function () { resolve(false); }); }); }; /** * Ensure the temporary session module is up-to-date for external components to consume * @returns {Promise} Promises a module id which can be either: * - Temporary session module id (authoring) * - Embedded module id (consumption on shaped data source) * - Original assert id (consumption on non-shaped data source) */ ShapingModelManager.prototype.saveTemporaryModule = function saveTemporaryModule() { var _this2 = this; if (this.tempModuleLoaded) { return this.tempModuleLoaded.then(function () { return _this2._shapingHelper.saveSessionModule(); }); } return Promise.resolve(this._shapingHelper && this._shapingHelper.getModuleId()); }; /** * Clears any previously loaded temporary module */ ShapingModelManager.prototype.clearTemporaryModule = function clearTemporaryModule() { this.tempModuleLoaded = null; }; /** * When a temporary module is loaded and completed * @return {Promise} resolved when temporary module is loaded or temporary module is not used */ ShapingModelManager.prototype.whenTemporaryModuleReady = function whenTemporaryModuleReady() { return this.tempModuleLoaded || Promise.resolve(); }; /** * Creates a moserJS module object using the shapingHelper * @param {Object} moduleId option id to be used to load the module from - used if we already have the temporary module * @param {Object} options.forceTemporaryModule --> Force temp module; even in consume * @return {Promise} A promise which will get resolved with the module object */ ShapingModelManager.prototype._loadModule = function _loadModule(moduleId) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { forceTemporaryModule: false }; // Most paths leads us to a temp module, default it to true this._embeddedModule = true; this._queryTypeParam = 'module'; var isAuthoringMode = this.dashboardApi.getMode() === 'authoring'; var optimizedMetadataIdList = this.getMetadataSubsetIds(); var forceTemporaryModule = options.forceTemporaryModule; if (this.sourceModel.hasInlineModule()) { ////////////////////////// // # CASE 5: Inline module spec for CDE return this._shapingHelper.createInlineModule(this.sourceModel.module, this.shapingModel.moserJSON); } else { ////////////////////////// // # CASE 4: Data source IS shaped, dashboard is loaded in authoring mode. // Temporary session module is created based off the embedded module if (this.shapingModel.moserJSON && (!isAuthoringMode && forceTemporaryModule || !this.shapingModel.embeddedModuleUpToDate || isAuthoringMode)) { this.tempModuleLoaded = this._shapingHelper.loadFromJSON(this.shapingModel.moserJSON); return this.tempModuleLoaded; } if (moduleId) { this.shapingModel.set({ shapingId: '__initialShapingID__' }, { silent: true }); //TODO Verify the use case and validate the inputs return this._shapingHelper.loadFromModuleId(moduleId, this.sourceModel.type, optimizedMetadataIdList); } ////////////////////////// // # CASE 3: Data source is NOT shaped, dashboard is loaded in authoring mode. // Temporary session module is created based off the original asset // Create a temp module if we're in authoring and useTempModule wasn't set to false, or if useTempModule was set to true if (!isAuthoringMode && forceTemporaryModule || isAuthoringMode && this.sourceModel.useTempModule !== false || this.sourceModel.useTempModule === true) { this.tempModuleLoaded = this._shapingHelper.addSource({ id: this.sourceModel.get('assetId'), href: this.sourceModel.get('searchPath'), type: MAP_SOURCETYPE_TO_USESPECTYPE[this.sourceModel.get('type')], name: this.sourceModel.get('name') }); return this.tempModuleLoaded; } ////////////////////////// // # CASE 2: Data source IS shaped, dashboard is loaded in consumption mode. // Embedded module is loaded. Embedded module is stored as an attachment of the dashboard in CM. this._embeddedModule = false; if (this.shapingModel.embeddedModuleId) { return this._shapingHelper.loadFromModuleId(this.shapingModel.embeddedModuleId, 'module', optimizedMetadataIdList); } ////////////////////////// // # CASE 1: Data source is NOT shaped, dashboard is loaded in consumption mode. // We need to pass to MUI the right datasource type that is used inside Moser 'useSpec', instead of the CM datasource type var useSpecSourceType = MAP_SOURCETYPE_TO_USESPECTYPE[this.sourceModel.get('type')].value(); this._queryTypeParam = useSpecSourceType; return this._shapingHelper.loadFromModuleId(this.sourceModel.assetId, useSpecSourceType, optimizedMetadataIdList); } }; /** * Called by the tree/grid when a shaping change happens so we can add to our undo/redo stack * @param {Object} undoRedoObj Object that has an undo and redo method that we can call * @param {Object} payloadData Optional payload data */ ShapingModelManager.prototype.addToUndoStack = function addToUndoStack(undoRedoObj, payloadData) { var newShapingId = this.shapingModel._generateUniqueId('shaping'); // Will be passed back in the onUndoRedoShapingChange function call var callbackPayload = { undoRedoObj: undoRedoObj, prevValue: this.shapingModel.get('shapingId'), value: newShapingId }; var payload = { sender: 'ShapingModelManager', payloadData: payloadData ? payloadData : null, senderContext: { applyFn: this._onUndoRedoShapingChange.bind(this, callbackPayload) } }; this.shapingModel.set({ shapingId: newShapingId }, payload); }; /** * Called by our undoRedoController when the user does an undo/redo of a shaping change * @param {Object} callbackPayload Object that was build when adding the information to the undo stack * @param {String} value the new value for the shapingId */ ShapingModelManager.prototype._onUndoRedoShapingChange = function _onUndoRedoShapingChange(callbackPayload, value) { if (callbackPayload.prevValue === value) { callbackPayload.undoRedoObj.undo(); } else { callbackPayload.undoRedoObj.redo(); } // Since we're overriding the default handling of undo/redo make sure we update the model with the value this.shapingModel.set({ shapingId: value }, { payloadData: { skipUndoRedo: true } }); }; ShapingModelManager.prototype.getShapingHelper = function getShapingHelper() { return this._shapingHelper; }; ShapingModelManager.prototype.getLocalizedName = function getLocalizedName() { return this.sourceModelManager.getLocalizedName(); }; /** * Handles showing error messages when the module fails to load * @return {Promise} Promise that will resolve with an augmented error object once the message is shown */ ShapingModelManager.prototype._handleLoadError = function _handleLoadError(showErrorToast, err) { var _this3 = this; // If the temp module failed to load make sure we don't think we have one or we'll end up deleting the source module. this._embeddedModule = false; this.sourceModel.set({ state: 'error', errorCode: err.code }); return this.getLocalizedName().then(function (name) { var exists = _this3.sourceModelManager.cmObjectExists; err.sourceInfo = { exists: exists, name: name }; if (showErrorToast !== false) { // The missingDataSetData message should be displayed for error code MSR-GEN-0091 (data for the file does not exist) if (_this3._isMissingDatasetData()) { _this3._showToast(StringResources.get('missingDataSetData'), { type: 'warning', preventDuplicates: true }); } else { _this3._showToast(StringResources.get('errorLoadingSource', { sourceName: name })); } } return err; }).catch(function () { err.sourceInfo = { exists: false, name: _this3.sourceModel.name }; if (showErrorToast !== false) { if (_this3.sourceModel.name) { _this3._showToast(StringResources.get('errorLoadingSource', { sourceName: _this3.sourceModel.name })); } else { _this3._showToast(StringResources.get('errorLoadingSourceMetaData')); } } return err; }).finally(function () { throw err; }); }; ShapingModelManager.prototype._isMissingDatasetData = function _isMissingDatasetData() { return this.sourceModel.get('errorCode') === 'MSR-GEN-0091' || this.sourceModelManager.getState() === 'error' && this.sourceModelManager.getType() === 'dataSet2' && this.sourceModelManager.cmObjectExists; }; ShapingModelManager.prototype._showToast = function _showToast(msg, options) { var toastOptions = options || { type: 'error', preventDuplicates: true }; this.dashboardApi.showToast(msg, toastOptions); }; /** * @param {Object} dashboardInfo object containing optional information used to create headers in dss requestedVersion * @param isDirty - if the dashboard is currently in an unsaved state * @param dashboardId - dashboard store id * @param searchPath - dashboard search path. * @param {String} dataSearchPath - model search path * @return {Object} object which contains ajax options, such as headers */ ShapingModelManager.prototype._getDashboardOptions = function _getDashboardOptions(dashboardInfo, dataSearchPath, options) { var dbOptions = {}; dbOptions.ajaxOptions = { headers: { 'x-audit-type': dashboardInfo.type, // Only add id and search path if the dashboard is not in the dirty state 'x-audit-dashboardId': dashboardInfo.isDirty ? null : dashboardInfo.boardId, 'x-audit-searchPath': dashboardInfo.isDirty ? null : encodeURI(dashboardInfo.searchPath), 'x-audit-dataSearchPath': encodeURI(dataSearchPath), 'x-senderId': options ? options.senderId : null } }; if (options && options.lastModified) { dbOptions.ajaxOptions.headers['x-model-last-modified'] = options.lastModified; } if (options && options.queryUserId) { dbOptions.ajaxOptions.headers['x-queryuserid'] = options.queryUserId; } return dbOptions; }; /** * Queries DSS for data * @param {String} querySpec the query specification sent to DSS * @param {Object} options * * @return {Promise} */ ShapingModelManager.prototype.queryData = function queryData(querySpec, options) { return this._queryData(querySpec, this._shapingHelper.getData.bind(this._shapingHelper), options); }; /** * Query DSS for predict data * @param {String} querySpec the predict query specification sent to DSS * @param {Object} options * * @return {Promise} */ ShapingModelManager.prototype.queryPredictData = function queryPredictData(querySpec, options) { return this._queryData(querySpec, this._shapingHelper.getPredictData.bind(this._shapingHelper), options); }; /** * Query relationship (Smarts Explore) * @param {JSONObject} querySpec * @param {Object} options * * @return {Promise} */ ShapingModelManager.prototype.querySmartsExploreRelationshipData = function querySmartsExploreRelationshipData(querySpec, options) { //TODO: Currently, this is called by Explorer to query smarts/relatonship endpoint. //Before a common 'prompt error response handler component' is implmented. Explorer //forwards queryspec to MUI and delegates MUI to send the request so that it can reuse the //DB signon and ambiguous connections signon handlers. return this._queryData(querySpec, this._shapingHelper.getExploreRelationshipData.bind(this._shapingHelper), options); }; ShapingModelManager.prototype._queryData = function _queryData(querySpec, getData) { var _this4 = this; var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; return this.load().then(this.sourceModelManager.getSearchPath.bind(this.sourceModelManager)).then(function (searchPath) { /** * We need to find the dashboard information needed for auditing. * dashboardInfo object containing optional information used to create headers in dss requestedVersion * @param dashboardInfo.isDirty - if the dashboard is currently in an unsaved state * @param dashboardInfo.dashboardId - dashboard store id * @param dashboardInfo.searchPath - dashboard search path. */ var dashboardInfo = _this4.dashboardApi.getDashboardInfo(); return new Promise(function (resolve, reject) { try { getData(querySpec, 'rawResponse', _this4._queryTypeParam, _this4._getDashboardOptions(dashboardInfo, searchPath, options), options.qfb, options.qfbMode).then(resolve).catch(function (error) { // Workaround since MUI changes the glass error response. Reset this property so our error handling // code will still work error.responseJSON = error ? error.data : {}; if ((typeof querySpec === 'undefined' ? 'undefined' : _typeof(querySpec)) === 'object') { error.querySpec = querySpec; } else if (querySpec) { try { // Keep track of the query spec that caused the http error error.querySpec = JSON.parse(querySpec); } catch (e) { _this4.logger.error(e); } } reject(error); }); } catch (error) { reject(error); } }); }); }; ShapingModelManager.prototype.moduleEventListener = function moduleEventListener(notification) { if (this._ignoreMoserEvents) { return; } switch (notification.getType()) { case MoserJS.default.Notification.ADD: //when undo removing of a calculated member, and calculated member is referenced in a widget, the widget will be refershed if (notification.getFeatureID() === MoserJS.default.Features.FILTER || notification.getFeatureID() === MoserJS.default.Features.ITEM || notification.getFeatureID() === MoserJS.default.Features.CALCULATION) { //moser throws a lot of events for a single action, so we only want to notify at the end of thread execution if (!this._hasDebounceNotify) { this._debounceShapingNotification = this._createDebouncedShapingNotification(); } this._debounceShapingNotification({ notification: notification, action: 'ADD' }); } break; case MoserJS.default.Notification.SET: case MoserJS.default.Notification.REMOVE: { this.clearCache(); if (!this._hasDebounceNotify) { var featureID = notification.getFeatureID(); if (featureID === MoserJS.default.Features.TYPE || featureID === MoserJS.default.Features.SEARCH_PATH || featureID === MoserJS.default.Features.STORE_ID || this._isMembersFolderExpansionNotification(notification)) { return; } this._debounceShapingNotification = this._createDebouncedShapingNotification(); } var action = notification.getType() === MoserJS.default.Notification.SET ? 'SET' : 'REMOVE'; this._debounceShapingNotification({ noRefresh: this._checkNotificationProperties(notification, 'propertySetByUser_label'), notification: notification, action: action }); } break; default: break; } }; //check if propertyName is present in notification passed in ShapingModelManager.prototype._checkNotificationProperties = function _checkNotificationProperties(notification, propertyName) { var notifier = typeof notification.getNotifier === 'function' && notification.getNotifier(); if (notifier && notifier.property) { //return true if specific property name is present in property array return _.some(notifier.getProperty(), function (prop) { return typeof prop.getName === 'function' && prop.getName() === propertyName; }); } else { return notifier && typeof notifier.getName === 'function' && notifier.getName() === propertyName; } }; // When the folder "Members" is expanded for the first time (lazy loaded), MUI sends a notification to tell "memberHidden" is changed from value "true" to "false". // We should not react to this notification (and more importantly should not trigger shaping model change event) as it has no impact to dashbboard. // This method checks if the notification is generated from this action to ignore it. ShapingModelManager.prototype._isMembersFolderExpansionNotification = function _isMembersFolderExpansionNotification(notification) { if (notification.getNotifier && notification.getNotifier() && notification.getNotifier().name === 'memberHidden') { if (notification.oldValue === 'true' && notification.newValue === 'false') { return true; } } return false; }; ShapingModelManager.prototype._createDebouncedShapingNotification = function _createDebouncedShapingNotification() { var _this5 = this; this._hasDebounceNotify = true; return _.debounce(function (payload) { payload = payload || {}; payload.sender = {}; var timeStamp = new Date().toISOString(); _this5.sourceModelManager.modificationTime = timeStamp; _this5.sourceModel.set({ modificationTime: timeStamp }, { silent: true }); if (payload.notification.newValue || payload.notification.oldValue) { payload.sourceId = _this5.sourceModel.id; var jsonParseString = function jsonParseString(value) { if (typeof value === 'string') { // This might be a JSON string or just a string - we have no way of knowing. try { return JSON.parse(value.trim()); } catch (e) { return value; } } return value; }; payload.newValue = jsonParseString(payload.notification.newValue); payload.oldValue = jsonParseString(payload.notification.oldValue); } _this5.shapingModel.trigger('shapingmodel:changed', payload); _this5._hasDebounceNotify = false; if (_this5._unitTestResolve) { _this5._unitTestResolve(); } }, 0); }; ShapingModelManager.prototype.getModuleId = function getModuleId() { return this._shapingHelper.context.store.moduleId; }; /** * Returns a MetadataColumn object * @param {String} id The Moser object idForExpression for which to get the metadata for * @param {boolean} logNotFound - boolean to log error in console when moser object not found or not * @return {MetadataColumn} Object that has APIs to get at the column metadata */ ShapingModelManager.prototype.getMetadataColumn = function getMetadataColumn(id) { var logNotFound = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (id && id.indexOf(SlotAPIHelper.MULTI_MEASURES_SERIES) !== 0) { if (!this.metadataColumnCache[id]) { var moserObject = this.getMoserObject(id); if (!moserObject) { if (logNotFound && this.logger) { this.logger.error('Could not find moser object with id ' + id, this); } return null; } this.metadataColumnCache[id] = new MetadataColumn({ moserObject: moserObject, sourceId: this.sourceModel.id, queryService: this.queryService }); } } else { return null; } return this.metadataColumnCache[id]; }; /** * Called by the shaping moserVisitor. Mostly broken out into its own funciton for easy unit test * @param {[MetadataColumns]} metadataColumns The resulting array of metadata columns * @param {Function} Callback to call with each metadataColumn. Callback should return true if the item should be part of the resulting array or false otherwise * @param {Boolean} breakOnFirstMatch default to false. If true will stop the search once the first item is found. * @param {Obj} moserObj The moser object which we're currently visiting * @return true if we should continue visiting the child or moserVistor.BREAK if we should stop visiting */ ShapingModelManager.prototype._moserVisitorCallback = function _moserVisitorCallback(metadataColumns, callback, breakOnFirstMatch, moserObj) { var isHidden = moserObj.hidden || moserObj.isHidden && moserObj.isHidden(); if (isHidden) { return false; } if (UNWANTED_MOSER_OBJECT_TYPES_FOR_METADATACOLUMNS.indexOf(moserObj.getObjectType()) === -1) { var id = moserObj.getIdForExpression ? moserObj.getIdForExpression() : null; if (id) { var metadataColumn = this.metadataColumnCache[id] || new MetadataColumn({ moserObject: moserObj, sourceId: this.sourceModel.id, queryService: this.queryService }); this._loadedMetadataColumns[id] = metadataColumn; if (callback(metadataColumn)) { metadataColumns.push(metadataColumn); if (breakOnFirstMatch) { return Shaping.moserVisitor.BREAK; } } } } return true; }; /** * [description] * @param {Function} Callback to call with each metadataColumn. Callback should return true if the item should be part of the resulting array or false otherwise * @param {Boolean} breakOnFirstMatch default to false. If true will stop the search once the first item is found. * @return {[MetadataColumns]} Array of MetadataColumn objects */ ShapingModelManager.prototype.getMetadataColumns = function getMetadataColumns(callback, breakOnFirstMatch) { if (!this.metadataColumns || callback) { this.metadataColumns = []; Shaping.moserVisitor(this.module.metadataTreeView, this._moserVisitorCallback.bind(this, this.metadataColumns, callback, breakOnFirstMatch)); } return this.metadataColumns; }; /** * Returns a MoserJS object * @param {String} id The Moser object idForExpression for which to get the metadata for * @return a MoserJS object */ ShapingModelManager.prototype.getMoserObject = function getMoserObject(id) { return MoserJS.default.ModuleUtils.getMoserObjectByIdForExpression(id, this.module); }; ShapingModelManager.prototype.clearCache = function clearCache() { this.metadataColumnCache = {}; }; ShapingModelManager.prototype.relink = function relink(sourceModel) { var _this6 = this; var relinkPromise = void 0; // ignore events fired from MOSER while relink is processiong this._ignoreMoserEvents = true; if (!this.module) { //this happens when open a saved dashboard where its data source had been deleted from CM if (this.shapingModel.moserJSON) { this._updateUseSpecInMoserJSON(sourceModel); relinkPromise = this._shapingHelper.loadFromJSON(this.shapingModel.moserJSON); } else { relinkPromise = this._shapingHelper.addSource({ id: sourceModel.get('assetId'), href: sourceModel.get('searchPath'), type: MAP_SOURCETYPE_TO_USESPECTYPE[sourceModel.get('type')], name: sourceModel.get('name') }); } } else { this.clearCache(); relinkPromise = this._shapingHelper.relink({ id: sourceModel.get('assetId'), href: sourceModel.get('searchPath'), type: MAP_SOURCETYPE_TO_USESPECTYPE[sourceModel.type] }); } return relinkPromise.then(function (module) { _this6.module = module; _this6.shapingModel.moserJSON = MoserJS.default.ModuleUtils.prepareModuleForSave(module); var payload = void 0; if (sourceModel && SOURCE_TYPES_TO_UPDATE.indexOf(sourceModel.type) !== -1) { payload = { sourceId: sourceModel.id, relinkIdForExpressions: function relinkIdForExpressions() { var itemIds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; return MoserJS.default.ModuleUtils.relinkIdForExpressions(null, itemIds, _this6.module); } }; } // After a link any embedded module we have is not longer up to date. Flag it // as such so it'll get updated during a save operation. _this6.shapingModel.setEmbeddedModuleUpToDate(false); return payload; }).catch(this._reloadMetadataErrorHandler.bind(this)).finally(function () { _this6._ignoreMoserEvents = null; }); }; ShapingModelManager.prototype._updateUseSpecInMoserJSON = function _updateUseSpecInMoserJSON(sourceModel) { var objectFactory = MoserJS.default.createObjectFactory(); var jsonObjectFactory = MoserJS.default.createJSONObjectFactory(objectFactory); var module = jsonObjectFactory.createModule(this.shapingModel.moserJSON); var useSpec = module.getUseSpec()[0]; useSpec.setSearchPath(sourceModel.get('searchPath')); useSpec.setStoreID(sourceModel.get('assetId')); useSpec.setType(MAP_SOURCETYPE_TO_USESPECTYPE[sourceModel.type]); this.shapingModel.moserJSON = MoserJS.default.ModuleUtils.prepareModuleForSave(module); }; ShapingModelManager.prototype.reloadMetadata = function reloadMetadata() { this.clearCache(); // If we just reloaded the metadata for a dataset that had no data in the first place // then we need to reload the dashboard so that the module and shapingHelper get initialized correctly if (this._isMissingDatasetData()) { return this.dashboardApi.reloadFromJSONSpec(this.dashboardApi.getSpec()); } return this._shapingHelper.reloadMetadata().then(this._postReloadMetadataHandler.bind(this, null)).catch(this._reloadMetadataErrorHandler.bind(this)); }; ShapingModelManager.prototype.getDrillGroups = function getDrillGroups() { return this.module.getDrillGroup(); }; ShapingModelManager.prototype._reloadMetadataErrorHandler = function _reloadMetadataErrorHandler(error) { //always trigger the event as the widget need to the show error this.shapingModel.trigger('shapingmodel:changed', { error: error }); throw error; }; ShapingModelManager.prototype._postReloadMetadataHandler = function _postReloadMetadataHandler(sourceModel, module) { //set the module again because it might be a new one if there is: a session timeout this.module = module; var payload = { sender: {} }; this.shapingModel.trigger('shapingmodel:changed', payload); }; ShapingModelManager.prototype.getDrillGroupsForColumn = function getDrillGroupsForColumn(columnId) { var drillGroups = this.getDrillGroups(); if (!drillGroups || drillGroups.length === 0) { return null; } return drillGroups.filter(this._containsSelectedColumn.bind(this, columnId)); }; ShapingModelManager.prototype._containsSelectedColumn = function _containsSelectedColumn(sourceColumnId, drillGroup) { return this._findIndex(drillGroup.getSegment(), 'ref', sourceColumnId) !== -1; }; /* * Returns the index at which the object with specified property value is found in the array */ ShapingModelManager.prototype._findIndex = function _findIndex(arrayObjects, sProperty, sValue) { if (!arrayObjects) { return -1; } for (var idx = 0; idx < arrayObjects.length; idx++) { if (arrayObjects[idx][sProperty] === sValue) { return idx; } } return -1; }; ShapingModelManager.prototype.addCalculation = function addCalculation(properties) { var calculationExpression = this._shapingHelper.expressionCreateObjectSimpleCalculation(properties, null); calculationExpression.setRegularAggregate(MoserJS.default.RegularAggregateType.CALCULATED); return this._shapingHelper.expressionAddToModuleWithValidation(calculationExpression).then(function () { return calculationExpression.idForExpression; }); }; /** * Returns an array of table names for the module * * @return {array} An array of table names */ ShapingModelManager.prototype.getTableNames = function getTableNames() { var _this7 = this; if (!this.tableNames) { this.tableNames = []; if (this.module) { var qs = this.module.getQuerySubject(); qs.forEach(function (subject) { _this7.tableNames.push(subject.getIdentifier()); }); } } return this.tableNames; }; /** * Return a bolean indicating whether 2 tables have a join relationship or not * * @param {string} firstTable - the name of the first table to verify * @param {string} secondTable - the name of the table to verify against firstTable */ ShapingModelManager.prototype.tablesHaveJointRelationship = function tablesHaveJointRelationship(firstTable, secondTable) { if (firstTable && firstTable.length && secondTable && secondTable.length) { if (firstTable === secondTable) { //If both names are identical return true if the name exists for the module var tables = this.getTableNames(); return tables.indexOf(firstTable) !== -1; } else { var tableRelationships = this._getRelationships(); if (tableRelationships) { var isMatched = function isMatched(relationship, value, isLeft) { return isLeft ? relationship.left === value : relationship.right === value; }; var jointRelationship = _.find(tableRelationships, function (relationship) { return isMatched(relationship, firstTable, true) && isMatched(relationship, secondTable, false) || isMatched(relationship, firstTable, false) && isMatched(relationship, secondTable, true); }); return jointRelationship ? true : false; } } } }; ShapingModelManager.prototype._getRelationships = function _getRelationships() { var _this8 = this; if (!this.tableRelationships) { var existMap = {}; this.tableRelationships = []; var relationships = this.module.getRelationship(); if (relationships && relationships.length) { relationships.forEach(function (relationship) { var left = relationship.getLeft(); var right = relationship.getRight(); if (left && right) { var leftRef = left.getRef(); var rightRef = right.getRef(); if (leftRef && rightRef) { var key = leftRef + rightRef; if (!existMap[key]) { existMap[key] = true; _this8.tableRelationships.push({ left: leftRef, right: rightRef }); } } } }); } } return this.tableRelationships; }; /** * Returns the temp module JSON */ ShapingModelManager.prototype.getTemporarySessionModuleJSON = function getTemporarySessionModuleJSON() { return this.module.toJSON(); }; /** * * Returns the JSON of a partial copy of the current Moser module containing only the query subjects for the given * columnIds and/or calculation. This is required for Smarts alternate vis recommender. * * @param {array} An array of idForExpressions of columns */ ShapingModelManager.prototype.copyPartialMoserModuleJSON = function copyPartialMoserModuleJSON(columnIds) { var _this9 = this; var factory = MoserJS.default.createObjectFactory(); var newModule = MoserJS.default.ModuleUtils.createModule(factory, this.module.getLabel(), this.module.getExpressionLocale()); var relationships = this.module.getRelationship(); if (relationships.length) { relationships.forEach(function (relationship) { return newModule.addRelationship(relationship.clone()); }); } var querySubjects = []; var querySubjectInNewModule = function querySubjectInNewModule(querySubject) { return querySubjects.indexOf(querySubject.getIdentifier()) !== -1; }; var isCalculation = function isCalculation(column) { return column.getObjectType() === MoserJS['default'].MoserObjectTypes.CALCULATION; }; columnIds.forEach(function (columnId) { var metadataColumn = _this9.getMetadataColumn(columnId); if (metadataColumn) { var querySubject = metadataColumn.getTable(); if (querySubject && !querySubjectInNewModule(querySubject)) { var clonedQS = querySubject.clone(); querySubjects.push(querySubject.getIdentifier()); MoserJS.default.ModuleUtils.removeMembers(clonedQS); //TODO: Temporary - see https://jsw.ibm.com/browse/CADBLW-665 newModule.addQuerySubject(clonedQS); } else if (!querySubject && isCalculation(metadataColumn)) { newModule.addCalculation(metadataColumn.getMoserObject().clone()); } else if (!querySubject && !isCalculation(metadataColumn)) { _this9.logger.error('Unable to add ' + columnId + ' to the module', _this9); } } }); return newModule.toJSON(); }; ShapingModelManager.prototype._getWidgetSubsetIds = function _getWidgetSubsetIds(spec) { var _this10 = this; var itemIds = []; var widgetSpecs = _.flatten([spec.layout ? DashboardSpecUtil.getWidgetModelList(spec.layout) : [], spec.widgets ? _.values(spec.widgets) : [] //Legacy (pre-11.1.7) specs ]); _.each(widgetSpecs, function (widget) { // collect all data items of widget var dataItems = _.chain(widget.data).compact().flatten() // only those using the current source .filter(function (view) { return view.modelRef === _this10.sourceModel.id; }).pluck('dataItems').compact().flatten().filter(function (dataItem) { return dataItem.itemId !== SlotAPIHelper.MULTI_MEASURES_SERIES; }).value(); if (dataItems.length > 0) { // include itemId,seletion context itemId, and customGroup itemId among dataItems itemIds.push.apply(itemIds, _.pluck(dataItems, 'itemId')); itemIds.push.apply(itemIds, _.chain(dataItems).pluck('selection').compact().flatten().pluck('context').compact().flatten().pluck('itemId').value()); itemIds.push.apply(itemIds, _.chain(dataItems).pluck('originalCustomGroupItemId').compact().flatten().value()); // include local filter columnIds if (widget.localFilters) { itemIds.push.apply(itemIds, _this10._getWidgetLocalFilterSubsetIds(widget.localFilters)); } // include saved prompt columnIds if (widget.savedPrompts) { itemIds.push.apply(itemIds, _this10._getWidgetSavedPromptsSubsetIds(widget.savedPrompts)); } } }); return itemIds; }; ShapingModelManager.prototype._getWidgetLocalFilterSubsetIds = function _getWidgetLocalFilterSubsetIds(localFilters) { var _this11 = this; var ids = []; _.each(localFilters, function (filter) { // filter consists with: // operator, values and columnId (optional) // when columnId is omitted, the values would contain the compound filter if ('columnId' in filter) { ids.push(filter.columnId); } else if ('operator' in filter) { // handle the nested filters recurssively ids.push.apply(ids, _this11._getWidgetLocalFilterSubsetIds(filter.values)); } }); return ids; }; ShapingModelManager.prototype._getWidgetSavedPromptsSubsetIds = function _getWidgetSavedPromptsSubsetIds(savedPrompts) { var ids = []; for (var key in savedPrompts) { if (savedPrompts[key]['modelFilterItem']) { ids.push(savedPrompts[key]['modelFilterItem']); } } return ids; }; ShapingModelManager.prototype._getPageContextSubsetIds = function _getPageContextSubsetIds(spec) { var _this12 = this; return _.chain(spec.pageContext) // only those using the current source .filter(function (pageContext) { return pageContext.sourceId === _this12.sourceModel.id; }) // for each page context hierarchyUniqueNames .pluck('hierarchyUniqueNames').compact().flatten().value(); }; ShapingModelManager.prototype.getMetadataSubsetIds = function getMetadataSubsetIds() { // no need for optimization on authoring mode // OR temporary module is already loaded // OR dealing with inline module (DaaS) if (this.dashboardApi.getMode() === 'authoring' || this.tempModuleLoaded || this.sourceModel.hasInlineModule()) { return; } if (!this.metatdataSubsetIds) { var boardModel = this.dashboardApi.getFeature('internal').getBoardModel(); var dataSourcesMoser = this.dashboardApi.getFeature('DataSources.moser'); // During upgrade we won't have a boardModel yet. if (boardModel) { var spec = this.dashboardApi.getFeature('Serializer').toJSON(); // during the upgrade the spec is expected to be undefined if (spec) { // merge the widget and page context subset item ids // @todo the first two should register as metadataSubsetIds providers // so these direct dependancies are eliminated var itemIds = []; itemIds.push.apply(itemIds, this._getWidgetSubsetIds(spec)); itemIds.push.apply(itemIds, this._getPageContextSubsetIds(spec)); itemIds.push.apply(itemIds, dataSourcesMoser.getMetadataSubsetIds()); this.metatdataSubsetIds = _.uniq(itemIds); } } } return this.metatdataSubsetIds; }; return ShapingModelManager; }(); return ShapingModelManager; }); //# sourceMappingURL=ShapingModelManager.js.map