123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- '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: BI Cloud (C) Copyright IBM Corp. 2018, 2020
- * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
- */
- define(['underscore', './util/DashboardSpecHelper', '../app/nls/StringResources'], function (_, DashboardSpecHelper, StringResources) {
- return function () {
- /**
- * @classdesc CopPasteController
- * Used for copying and pasting widgets across different CA dashboards
- * Copy flow for DB => select widgets from DB spec => store in glass clipboard service in shared spec obj => show toast msgs
- * Copy flow for siblings => Use this classes doCopy method and provide a overwriteSpec
- * Paste flow for DB => get data from clipboard service => convert via conversion service => render widget & show toast msgs
- * Paste flow for children => provide a overwriteTarget to doPaste => accept returned converted spec and render appropriately; doPaste will show toast msgs
- * @param {Object} options Expects layoutController, dashboardAPI, logger, model, api
- */
- function CopyPasteController(options) {
- _classCallCheck(this, CopyPasteController);
- this.layoutController = options.layoutController;
- this.dashboardApi = options.dashboardApi;
- this.logger = options.logger;
- this.model = options.model;
- this.api = options.api;
- this.specType = options.type || 'DASHBOARD';
- this.clipboard = this.dashboardApi.getGlassCoreSvc('.Clipboard');
- }
- CopyPasteController.prototype.destroy = function destroy() {
- this.layoutController = null;
- this.dashboardApi = null;
- this.logger = null;
- this.model = null;
- this.api = null;
- this.clipboard = null;
- };
- CopyPasteController.prototype.getSpecHelper = function getSpecHelper() {
- var _this = this;
- if (this.specHelper) {
- return Promise.resolve(this.specHelper);
- }
- return this.layoutController.getInteractionController().then(function (controller) {
- _this.specHelper = new DashboardSpecHelper(controller);
- return _this.specHelper;
- });
- };
- /**
- * Handles the copy gesture by getting the spec and saving it
- * @param {Object} overwriteSpec Spec obj with {type: str, spec: json str, count: number}
- * @return {Promise} Returns a promise which will resolve after glass
- */
- CopyPasteController.prototype.doCopy = function doCopy(overwriteSpec) {
- var _this2 = this;
- return this.getSpecHelper().then(function (specHelper) {
- var currentTime = new Date().getTime(),
- sharedSpecObj = void 0;
- var overwriteSpecIsValid = function overwriteSpecIsValid(spec) {
- return spec && spec.type !== 'DASHBOARD' && spec.count;
- };
- // make sure incoming overwritespec fits our shared criteria
- if (overwriteSpecIsValid(overwriteSpec)) {
- sharedSpecObj = overwriteSpec;
- } else {
- sharedSpecObj = _this2.selectFromSpecForCopyPaste(specHelper);
- sharedSpecObj.type = _this2.specType;
- }
- sharedSpecObj.timestamp = currentTime; // Set timestamp for Reporting (TODO: verify and remove in the future)
- return _this2.clipboard.set(sharedSpecObj).then(function () {
- if (sharedSpecObj.errMsg) {
- _this2._showToast(sharedSpecObj.errMsg);
- }
- if (sharedSpecObj.count > 0) {
- return _this2._showToast('copy', { count: sharedSpecObj.count });
- }
- return null;
- });
- }).catch(function (err) {
- return _this2._showError('specCopyErr', err);
- });
- };
- /**
- * Handles the paste gesture by getting the spec from localstorage, validating it then performs the paste
- * @param {String} overwriteTarget return a converted spec for a different target; Requires a converter registered with the conversion-service with this target
- * @return {Promise} Returns a promise which will resolve after glass
- */
- CopyPasteController.prototype.doPaste = function doPaste(overwriteTarget) {
- var _this3 = this;
- if (overwriteTarget) {
- return this.getConvertedSpec(overwriteTarget).then(function (spec) {
- return spec;
- }).catch(function (err) {
- return _this3._showError('specConvertErr', err);
- });
- }
- return this.getConvertedSpec(this.specType).then(function (spec) {
- if (!spec) return;
- return _this3.getSpecHelper().then(function (specHelper) {
- return specHelper.validateDashboardSpec(spec);
- });
- }).then(function (spec) {
- if (!spec) return;
- // remove any current selections
- _this3.layoutController.interactionController.selectionHandler.deselectAll();
- return _this3._performPaste(spec);
- }).catch(function (err) {
- return _this3._showError('specConvertErr', err);
- });
- };
- /**
- * Retrieves a converted spec, running through the clipboard and conversion services
- * @returns {Promise} resolves to return the minimized spec selected widget(s) as a JSON string
- */
- CopyPasteController.prototype.getConvertedSpec = function getConvertedSpec(target) {
- var _this4 = this;
- return this.clipboard.get().then(function (spec) {
- return _this4._doConvert(spec, target);
- });
- };
- CopyPasteController.prototype._doConvert = function _doConvert(spec, target) {
- var src = spec.type;
- var data = spec.spec;
- if (src == 'REPORT') {
- this._showError('pasteNotSupportedErr', { name: StringResources.get('reportParam') });
- return null;
- }
- return src === target || !data ? Promise.resolve(data) : this.dashboardApi.getGlassSvc('.ConversionService').then(function (srvc) {
- return srvc.convert(src, target, data);
- });
- };
- /**
- * Selects the widgets to copy
- * @returns returns the minimized spec selected widget(s) as JSON
- */
- CopyPasteController.prototype.selectFromSpecForCopyPaste = function selectFromSpecForCopyPaste(specHelper) {
- var returnObject = {
- spec: null,
- count: 0
- };
- var selectedContent = this.dashboardApi.getCanvas().getSelectedContentList();
- if (selectedContent.length) {
- returnObject = specHelper.getContentsToJSONSpec();
- }
- return returnObject;
- };
- /**
- * TODO - look at organizing this better either by creating a separate class, or even consider how the spec fragment is being handled in general.
- * TODO - Currently, it is being extended on a need to basis, it might be better to copy the full spec and take what is needed based on some configuration file
- * Does the work of pasting widgets
- * @param {object} spec - spec representing what to paste
- * @return {Promise} Returns a resolved promise if all widgets are pasted correctly or there is nothing to paste
- */
- CopyPasteController.prototype._performPaste = function _performPaste(spec) {
- var _this5 = this;
- if (spec.nonMergedWidgets) {
- spec.widgets = spec.nonMergedWidgets;
- delete spec.nonMergedWidgets;
- }
- //let dockFiltersHandled = false;
- //let targetTabId = this.layoutController.getCurrentSubViewId && this.layoutController.getCurrentSubViewId();
- //let emptyDB = this.layoutController.boardModel && (this.layoutController.boardModel.pageContext.models && this.layoutController.boardModel.pageContext.models.length === 0) &&
- // (this.layoutController.boardModel.widgetInstances && Object.keys(this.layoutController.boardModel.widgetInstances).length === 0);
- var getLayout = function getLayout(idx) {
- return idx < spec.layout.length ? spec.layout[idx] : undefined;
- };
- var findObj = function findObj(ary, id) {
- return _.find(ary, function (ele) {
- return id === ele.id;
- });
- };
- function findSource(widget, sourceMap) {
- if (widget.data && widget.data.dataViews) {
- _.each(widget.data.dataViews, function (view) {
- var newSource = _.find(spec.dataSources.sources, function (source) {
- return source.id === view.modelRef;
- });
- sourceMap[view.modelRef] = newSource;
- });
- }
- }
- // finds a widget and associated data source
- function findWidget(id, widgets, sourceMap) {
- var widget = findObj(spec.widgets, id);
- if (widget) {
- // get datasource associated to widget
- findSource(widget, sourceMap);
- widgets.push(widget);
- }
- }
- // recursively finds a widgets and associated data sources
- function findWidgets(items, widgets, sourceMap) {
- _.each(items, function (item) {
- return item.items ? findWidgets(item.items, widgets, sourceMap) : findWidget(item.id, widgets, sourceMap);
- });
- }
- /*
- const determinePageContextForCopyToEmptyDB = () => {
- let pageContextTabFilters = [];
- _.each(spec.pageContext, (pageContext) => {
- if( pageContext.scope !== 'global') {
- pageContext.scope = targetTabId;
- }
- pageContextTabFilters.push(pageContext);
- });
- return pageContextTabFilters;
- };
- */
- /*
- const determinePageContextForCopyToSameDB = () => {
- let pageContextTabFilters = [];
- _.each(spec.pageContext, (pageContext) => {
- // if copying within the same DB, ignore global filters and only copy to a different tab
- if(pageContext.scope !== 'global' && targetTabId !== pageContext.scope) {
- // only add if it doesn't interfere with target tab's filters
- let currTabFilter = _.find(this.layoutController.boardModel.pageContext.models, (currPageContext) => {
- return currPageContext.scope === targetTabId && currPageContext.hierarchyUniqueNames[0] === pageContext.hierarchyUniqueNames[0];
- });
- // copy filters if there are no filters on the target tab or the existing filters does not use the same datasource dataitem
- if(!currTabFilter) {
- pageContext.scope = targetTabId;
- pageContextTabFilters.push(pageContext);
- }
- }
- });
- return pageContextTabFilters;
- };
- */
- /** This method will
- *
- * Same DB and Tab Same DB different tab Different (New) DB Different (Existing) DB
- * ---------------------- ---------------------------- ------------------- -----------------------
- * Tab Filters don't copy or convert convert if not page cxt filter on same DI copy as tab filter convert to local filters if not page cxt filter on same DI
- * Global Filters don't copy or convert don't copy or convert copy as global filter convert to local filters if not page cxt filter on same DI
- *
- * For filters with property 'isUserFilter', these are user defined filter so they must always be copied
-
- const handleFilters = () => {
- // if pasting to the same DB or new DB, handle filter otherwise keep filters as local filters which have already been added
- // to the widgets during the 'copy' action
- if(this.layoutController.topLayoutModel.id === spec.dashboardID || emptyDB) {
- // remove widget local filters generated by the 'copy' action, since they exist in the pageContext.
- _.each(fragmentModel.widgets, (widget) => {
- widget.localFilters = _.where( widget.localFilters, { isUserFilter: true });
- if (!widget.localFilters.length) {
- delete widget.localFilters;
- }
- });
- if(!dockFiltersHandled) {
- if(!emptyDB) {
- fragmentModel.pageContext = determinePageContextForCopyToSameDB();
- } else {
- fragmentModel.pageContext = determinePageContextForCopyToEmptyDB();
- }
- dockFiltersHandled = true;
- }
- } else {
- // remove any local filters generated by the 'copy' action that uses the same DI as the target's filters (if any). Only handle origin=filters, don't want to
- // remove local filters for pagecontext with origin=visualization
- let filters;
- let pcFiltersOnly = _.filter(this.layoutController.boardModel.pageContext.models, (pc) => {
- return pc.origin === 'filter' && (pc.scope === 'global' || pc.scope === targetTabId);
- });
- _.each(fragmentModel.widgets, (widget) => {
- if( widget.localFilters) {
- const filterColumns = _.chain(pcFiltersOnly).pluck('hierarchyUniqueNames').flatten().value();
- filters = _.filter(widget.localFilters, (filter) => {
- return filter.isUserFilter || filterColumns.indexOf(filter.columnId) < 0;
- });
- if (filters && filters.length) {
- widget.localFilters = filters;
- }
- }
- });
- }
- };
- */
- var getFragment = function getFragment(layout) {
- var fragmentModel = {};
- var content = null;
- if (_this5._isTypeRegistered(layout.type)) {
- content = layout;
- } else {
- fragmentModel.layout = layout;
- // get the widget
- if (spec.widgets && spec.widgets.length) {
- var widgets = [];
- var sourceMap = {};
- // handle grouping
- if (layout.items) {
- findWidgets(layout.items, widgets, sourceMap);
- } else {
- findWidget(layout.id, widgets, sourceMap);
- }
- if (widgets.length) {
- fragmentModel.widgets = widgets;
- fragmentModel.dataSources = {
- version: spec.dataSources.version,
- sources: _.values(sourceMap)
- };
- // TODO: instead of stripping out a known attribute, we should ignore anything runtime/transient attribute (eg. runtimeOnly flag) in the model.
- // When explicitScale is set to truthy, the VIDA normalization is not set; hence we does not want this property
- if (layout.content && layout.content.properties) {
- var contentProperties = Object.keys(layout.content.properties);
- if (contentProperties.indexOf('explicitScale') !== -1) {
- delete layout.content.properties['explicitScale'];
- }
- }
- }
- }
- if (spec.drillThrough && spec.drillThrough.length) {
- fragmentModel.drillThrough = spec.drillThrough;
- }
- if (spec.properties) {
- fragmentModel.properties = spec.properties;
- }
- if (spec.fredIsRed) {
- fragmentModel.fredIsRed = spec.fredIsRed;
- }
- if (spec.episodes) {
- fragmentModel.episodes = spec.episodes;
- }
- }
- /*
- Defect 252008. Commenting out this code pending future analysis. For now, we only want to simply copy local filters.
- if(spec.pageContext) {
- handleFilters();
- }
- */
- return {
- content: content,
- model: fragmentModel
- };
- };
- // recursively paste widgets into dashboard (if the dashboard is templated, then the widgets are placed in different slots)
- var apply = function apply(idx, transactionToken) {
- var layout = getLayout(idx);
- if (layout) {
- var fragment = getFragment(layout);
- fragment.copyPaste = true;
- // if there is a layout then there is a widget to paste
- if (fragment && (fragment.content || fragment.model && fragment.model.layout)) {
- return _this5.dashboardApi.getFeature('Canvas').addContent({ spec: fragment.content || fragment.model, copyPaste: true }, transactionToken).then(function (content) {
- var layoutModelId = content.getId();
- // TODO: layoutReady() is the better intended method for the behavior wanted here, it is not working as
- // expected, some investigation is required by Remi
- return _this5.layoutController.whenWidgetRenderComplete(layoutModelId).then(function (layout) {
- // select the pasted widget
- _this5.layoutController.interactionController.selectionHandler.selectNode(layout.domNode, {
- isTouch: false
- });
- });
- });
- } else {
- return Promise.reject(new Error('Unable to complete pasting of widget(s). Can not determine the layout fragment'));
- }
- } else {
- return Promise.reject(new Error('Unable to complete pasting of widget(s). Checking for a layout that is not there'));
- }
- };
- // in order to build a fragment model per layout, traverse the layout to get its widget(s), then get the datasources for the widgets
- if (spec) {
- if (spec.layout) {
- var promises = [];
- // create a transaction token in order for paste to be undone/redone with one click
- var transaction = this.dashboardApi.getFeature('Transaction');
- var transactionToken = transaction.startTransaction();
- for (var i = 0; i < spec.layout.length; i++) {
- promises.push(apply(i, transactionToken));
- }
- transaction.endTransaction(transactionToken);
- return Promise.all(promises);
- } else {
- return Promise.resolve();
- }
- } else {
- return Promise.resolve();
- }
- };
- CopyPasteController.prototype._isTypeRegistered = function _isTypeRegistered(type) {
- var contentTypeRegistry = this.dashboardApi.getFeature('ContentTypeRegistry');
- return contentTypeRegistry.isTypeRegistered(type);
- };
- // Log error messages and show toast
- CopyPasteController.prototype._showError = function _showError(errKey, err) {
- this.logger.error(StringResources.get(errKey), err);
- return this._showToast(errKey, err, {
- type: 'error',
- 'preventDuplicates': true
- });
- };
- // Show toast popup
- CopyPasteController.prototype._showToast = function _showToast(strServiceKey, strParams, toastParams) {
- var toastMsg = !strParams ? StringResources.get(strServiceKey) : StringResources.get(strServiceKey, strParams);
- return this.dashboardApi.showToast(toastMsg, toastParams);
- };
- return CopyPasteController;
- }();
- });
- //# sourceMappingURL=CopyPasteController.js.map
|