'use strict'; 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: Storytelling * (C) Copyright IBM Corp. 2015, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['baglass/core-client/js/core-client/errors/BaseError', 'baglass/core-client/js/core-client/utils/ClassFactory', 'underscore', 'gemini/app/util/ErrorUtils', 'gemini/app/ui/dialogs/GenericViewDialog', 'react', 'react-dom' // PLEASE PLEASE PLEASE - No Storytelling dependencies here. // This module is loaded in scope of dashboard and should be as light as possible. // Load storytelling modules on-demand, only when used. ], function (BaseError, ClassFactory, _, ErrorUtils, Dialog, React, ReactDOM) { var getContentView = function getContentView(glassContext) { var contentView = null; if (glassContext.appController.getCurrentContentView) { contentView = glassContext.appController.getCurrentContentView(); } else if (glassContext.appController.currentAppView.currentContentView) { contentView = glassContext.appController.currentAppView.currentContentView; } return contentView; }; var ActionHandler = function () { function ActionHandler() { _classCallCheck(this, ActionHandler); // This value comes from the dashboardToStory.json file in the perspectives directory this._contributionId = 'com.ibm.bi.storytelling'; } /** * Handles whether or not to show the menu item(s) we contribued to the save menu. * @override * * @returns {boolean} Returns true if item should be visible or false if not. */ ActionHandler.prototype.isItemVisible = function isItemVisible(context) { if (context.target.itemId === this._contributionId + '.exportAsStory') { return this._isBoardTypeSupported(context) && ErrorUtils.hasCapability(context.glassContext, 'canAuthorDashboard'); } return false; }; /** * Handles the click of the menu item we contributed to the save menu. * @override * * @returns {Promise} */ ActionHandler.prototype.onSelectItem = function onSelectItem(context) { if (this._contributionId + '.exportAsStory' === context.target.itemId) { var view = getContentView(context.glassContext); var segmentType = { type: 'Exported Object' }; view.getDashboardApi().getFeature('segment').track('exportedToStory', function () { return segmentType; }); return this._convertToStory(context); } return Promise.resolve(); }; /** * @param {Object} service This is the object that is defined when creating a new SaveAsDialog * @param {string} selection This comes from SaveAsDialog's inner workings * @param {string} name This comes from SaveAsDialog's inner workings * @param {boolean} overwrite This comes from SaveAsDialog's inner workings * @param {boolean} loadOnSave Whether to load after a successful export * * @returns {Promise} */ ActionHandler.prototype.exportAsStory = function exportAsStory(service, selection, name, overwrite, loadOnSave) { return this._sendRequest({ context: service.context, url: selection.url, name: name, overwrite: overwrite }).then(this._onExportSuccess.bind(this, service.context, loadOnSave)).catch(this._onExportError.bind(this)).finally(this._exportDialog.hide.bind(this._exportDialog)); }; /** * Handle the conversion of the board model to a story and displaying the SaveAs dialog * @private * @param {Object} context The current context of the app that the user has when the export menu item was clicked. * * @returns {Promise} */ ActionHandler.prototype._convertToStory = function _convertToStory(context) { /* * Steps in convert to story * 1. User clicks "Export as a story" * 2. User is prompted to select Story type (and template if selecting GJ) * 3. User is prompted to choose a location to save, name the story, and open automatically (default) or not * 4a. User is automatically brough to story (if option was chosen) * 4b. User is shown the success/green toast about the successful conversion * 4c. User is shown a dialog if trying to use the same name asking if she wants to overwrite the existing object * 4d. User is shown an error/red toast about any error that is returned during the process */ return this._openStoryTypeSelectionDialog(context).then(this._openSaveAsDialog.bind(this)); }; /** * Setup and display a dialog to the user to allow her to select the Story Navigation Model type * and, in the case of Guided Journey, select the layout template type. * @private * * @returns {Promise} */ ActionHandler.prototype._openStoryTypeSelectionDialog = function _openStoryTypeSelectionDialog(context) { var _this = this; return Promise.all([ClassFactory.loadModule('storytelling/glass/StoryLayoutPickerView'), ClassFactory.loadModule('storytelling/nls/StringResources')]).then(function (modules) { var StoryLayoutPickerView = modules[0]; var stringResources = modules[1]; return new Promise(function (resolve) { var dialogElement = document.createElement('div'); dialogElement.classList.add('templatePickerDialog'); var viewOptions = { action: function () { this._templateSelectDialog.hide(); this._templateSelectDialog.view.getSelectedLayoutSpec().then(function (_ref) { var layout = _ref.layout; // have to do layout.layout because layoutpickerview is made to return layouts // but we are now using it to return a layout + widgets resolve({ context: context, navModel: layout.layout.type }); }); }.bind(_this) }; _this._templateSelectDialog = new Dialog({ id: 'selectTemplateTitle', title: stringResources.get('selectTemplateLabel'), viewClass: StoryLayoutPickerView, viewOptions: viewOptions, okCallback: viewOptions.action, cancelCallback: function cancelCallback() { resolve({ cancelled: true }); }, buttons: [{ text: stringResources.get('selectLabel'), handler: viewOptions.action, type: 'primary', defaultId: 'select_button' }, 'cancel'], showCloseX: true, width: '760px' }); _this._templateSelectDialog.open(); }); }); }; /** * @private * * @param {Object} options An object of required values * @param {boolean} [options.cancelled] Set if the user clicks cancel on the export as a story dialog * @param {Object} options.context The current context of the application * @param {string} options.navModel The navigation model that was selected in the dialog */ ActionHandler.prototype._openSaveAsDialog = function _openSaveAsDialog(options) { var _this2 = this; if (options.cancelled && options.cancelled === true) { return Promise.resolve(); } var context = options.context; var navModel = options.navModel; var glassContext = context.glassContext; var contentView = getContentView(glassContext); var dashboardApi = contentView.getDashboardApi(); return Promise.all([ClassFactory.loadModule('storytelling/glass/ExportToStoryDialog'), ClassFactory.loadModule('storytelling/nls/StringResources'), ClassFactory.loadModule('storytelling-ui/storytelling-ui.min'), this._createStory(dashboardApi, contentView, navModel), this._getAncestors(context, contentView.getBoardId())]).then(function (_ref2) { var ExportDialog = _ref2[0], stringResources = _ref2[1], StorytellingUI = _ref2[2], modelObj = _ref2[3], ancestors = _ref2[4]; var modelJson = modelObj.model; _this2._exportDialog = new ExportDialog({ glassContext: glassContext, defaultFileName: stringResources.get('newDefaultStoryName', { modelName: contentView.boardModel.name }), service: { context: { glassContext: glassContext, boardModel: modelJson }, export: _this2.exportAsStory.bind(_this2) }, ancestors: ancestors, onHide: _this2._clearToast.bind(_this2) }); _this2._exportDialog.open(); if (modelObj.status.hasOutOfBoundsWidget) { _this2._showToast(StorytellingUI, stringResources.get('outOfBoundsWarningMessage')); } else if (modelObj.status.hasEmptyExploreCards) { _this2._showToast(StorytellingUI, stringResources.get('emptyExploreCardsWarningMessage')); } }); }; ActionHandler.prototype._createStory = function _createStory(dashboardApi, contentView, navModel) { var _this3 = this; return Promise.all([ClassFactory.loadModule('storytelling/StoryService')]).then(function (_ref3) { var StoryService = _ref3[0]; var storyService = new StoryService({ dashboardApi: dashboardApi }); return storyService.createStory({ boardModel: contentView.boardModel, layoutController: contentView.boardController.layoutController, targetInfo: { type: navModel, transition: 'none' }, sourceType: _this3.sourceType }); }); }; ActionHandler.prototype._showToast = function _showToast(StorytellingUI, message) { var toast = React.createElement(StorytellingUI.Toast, { contentString: message, statusType: 'warning', className: 'db-st-export-toast' }); // Parsing this._exportDialog.$el[0] to render is not ideal // In works in this case because the toast is added to the body regardless ReactDOM.render(toast, this._exportDialog.$el[0]); }; ActionHandler.prototype._clearToast = function _clearToast() { this._exportDialog && ReactDOM.unmountComponentAtNode(this._exportDialog.$el[0]); }; /** * @private */ ActionHandler.prototype._getAncestors = function _getAncestors(context, id) { var ancestors = function ancestors(id) { if (id) { return context.glassContext.getSvc('.Content').then(function (oContentService) { var server_URL = oContentService.getBaseObjectsURL() + '/' + id; return oContentService.get(server_URL, { data: { fields: 'ancestors' } }); }); } else { return Promise.resolve(null); } }; return ancestors(id).then(function (result) { return result ? result.data[0].ancestors || null : null; }); }; /** * @private */ ActionHandler.prototype._onExportSuccess = function _onExportSuccess(context, loadOnSave, response) { var appController = context.glassContext.appController; if (loadOnSave) { appController.openAppView('story', { perspective: 'story', content: { perspective: 'story', objectUrl: response.jqXHR.getResponseHeader('location'), isAuthoringMode: appController.getCurrentContentView().isAuthoringMode } }); } else { return ClassFactory.loadModule('storytelling/nls/StringResources').then(function (stringResources) { var perspective = getContentView(context.glassContext).perspective; appController.showToast(stringResources.get(perspective + '_export_success')); }); } }; /** * Handle the error that was returned when tyring to save the new story. * @private * @param {Object} errorObject An object from the rejected promise around the ajax.post in _exportAsStoryRequest * @returns {Promise} A rejected promise if it was a duplicate error or resolved promise otherwise */ ActionHandler.prototype._onExportError = function _onExportError(errorObject) { var errorCode = ErrorUtils.getErrorCode(errorObject.saveRequest); if (errorCode === 'cmDuplicateName') { return Promise.reject(new BaseError('duplicate', { 'isDuplicate': true })); } else { ErrorUtils.showError(errorObject.glassContext, errorObject.saveRequest, errorObject.cmSpec); return Promise.resolve(); } }; /** * Send an ajax post request to create a new story with the converted board model * @private * @see {@link _exportAsStory} for parameter descriptions * @param {Object} context * @param {string} url * @param {string} name * @param {boolean} overwrite * * @returns {Promise} A Promise that will be resolve when the save is complete or rejected if error during save */ ActionHandler.prototype._sendRequest = function _sendRequest(options) { var context = options.context; var url = options.url; var name = options.name; var overwrite = options.overwrite; var ajaxSvc = context.glassContext.getCoreSvc('.Ajax'); var spec = context.boardModel; if (name) { spec.name = name; } var cmSpec = this._getCMSpec({ spec: spec, context: context }); url = url + (overwrite ? '?updateAction=replace' : ''); return ajaxSvc.ajax({ type: 'post', url: url, contentType: 'application/json', processData: false, dataType: 'text', data: JSON.stringify(cmSpec) }).catch(function (errorObject) { return Promise.reject(new BaseError('saving story error', { cmSpec: cmSpec, glassContext: context.glassContext, saveRequest: errorObject.jqXHR })); }); }; /** * @private */ ActionHandler.prototype._getCMSpec = function _getCMSpec(options) { var spec = options.spec; var context = options.context; var cmSpec = { 'defaultName': spec.name, 'type': this._getAssetType(context), 'specification': JSON.stringify(spec), 'deploymentReferences': getContentView(context.glassContext).dashboardApi.getFeature('dataSources.deprecated').getDeploymentReferences(), 'iconURI': '#common-catalog', 'defaultScreenTip': 'story', 'tags': ['story'] }; return cmSpec; }; /** * @private */ ActionHandler.prototype._getAssetType = function _getAssetType(context) { var assetType = 'exploration'; //default asset type var content = getContentView(context.glassContext).getContent(); if (content.options && content.options.config) { var config = content.options.config; if (config.hasOwnProperty('assetType')) { assetType = config.assetType; } } return assetType; }; ActionHandler.prototype._isBoardTypeSupported = function _isBoardTypeSupported(context) { var view = getContentView(context.glassContext); this.sourceType = view.getDashboardApi && view.getDashboardApi().getType(); if (this.sourceType === 'dashboard') { // Only support tab layout and ignore infographic from dashboard return view.boardModel.layout.type === 'tab'; } else if (this.sourceType === 'explore') { return true; } return false; }; return ActionHandler; }(); return ActionHandler; }); //# sourceMappingURL=ExportAsStory.js.map