'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: 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', '../../app/util/GlassUtil', 'text!../schema/schemaDefs.json', 'text!../schema/schemaMappings.json', 'text!../schema/nameSchema.json', 'text!../schema/layoutSchema.json', 'text!../schema/layoutFragmentSchema.json', 'text!../schema/themeSchema.json', 'text!../schema/versionSchema.json', 'text!../schema/eventGroupsSchema.json', 'text!../schema/drillThroughSchema.json', 'text!../schema/propertiesSchema.json', 'text!../schema/pageContextSchema.json', 'text!../schema/widgetsSchema.json', 'text!../schema/widgetsFragmentSchema.json', 'text!../schema/widgetsTemplateSchema.json', 'jquery'], function (_, GlassUtil, SchemaDefs, SchemaMappings, NameSchema, LayoutSchema, LayoutFragmentSchema, ThemeSchema, VersionSchema, EventGroupsSchema, DrillThroughSchema, PropertiesSchema, PageContextSchema, WidgetsSchema, WidgetsFragmentSchema, WidgetsTemplateSchema, $) { var findObj = function findObj(ary, id) { return _.find(ary, function (ele) { return id === ele.id; }); }; var DashboardSpecHelper = function () { /** * Gets the spec content of a selected widget * @param {object} controller - the interaction controller (either a interaction/Controller or interaction/BaseController * from which the spec content is derived */ function DashboardSpecHelper(controller) { _classCallCheck(this, DashboardSpecHelper); // TODO lifecycle_cleanup -- removethe controller dependency here this.controller = controller; this.dashboardApi = controller.dashboardApi; this.glassContext = controller.glassContext; this.logger = this.glassContext && this.glassContext.getCoreSvc ? this.glassContext.getCoreSvc('.Logger') : null; } /** * Gets the spec content of a widget * @param {boolean} pinning - indicates if the call was triggered from pinning. If so, get more content * @param {ContentAPI} content - ContentApi object to get the contentSpec from (Used through api when content is not from selection) * @returns returns the spec content for each selected node as an array element */ DashboardSpecHelper.prototype.getContents = function getContents(pinning, content) { var _this = this; var specContents = []; var selectedContent = []; var internalAPI = this.dashboardApi.getFeature('internal'); var boardModel = internalAPI.getBoardModel(); var specVersion = boardModel.version; if (content) { selectedContent = [content]; } else { selectedContent = this.dashboardApi.getCanvas().getSelectedContentList(); } selectedContent.forEach(function (selectedContent) { var layoutModel = selectedContent.getFeature('Models.internal').getLayoutModel(); var layoutWidgetIds = layoutModel.listWidgets([layoutModel.id]); var layoutWidgets = []; var nonMergedWidgets = []; var timelineEpisodes = []; var sourceIds = []; var errMsg = void 0; // ALERT! WARNING! DANGER! `widgetModel` is set in the loop below, and the last-instance of it is consumed // when setting specContent.drillThrough. Screwy javascript scoping rules for `var` types hid this, // but it's almost certianly a bug in some context? var widgetModel = void 0; for (var j = 0; j < layoutWidgetIds.length; j++) { var widgetContent = _this.dashboardApi.getCanvas().getContent(layoutWidgetIds[j]) || selectedContent.getContent(layoutWidgetIds[j]) || selectedContent; var internalModels = widgetContent.getFeature('Models.internal'); widgetModel = internalModels.getWidgetModel(); var mergedModel = _this._addFilterToWidgetModel(widgetModel); // Copy scenario, but don't copy filter widgets since filters are not supported if (!pinning) { if (widgetModel.type !== 'filter') { nonMergedWidgets.push(widgetModel); layoutWidgets.push(mergedModel); } else { errMsg = 'filterWidgetCopyWarn'; } } else { layoutWidgets.push(mergedModel); } var timelineEpisodeForPinning = _this._getTimeLineEpisodeForPinning(boardModel, layoutWidgetIds[j]); if (timelineEpisodeForPinning) { timelineEpisodes.push(timelineEpisodeForPinning); } var viz = widgetContent.getFeature('Visualization'); if (viz) { var dataSource = viz.getDataSource(); var sourceId = dataSource ? dataSource.getId() : null; if (sourceId && sourceIds.indexOf(sourceId) === -1) { sourceIds.push(sourceId); } } } var dataSources = _this._getDataSources(sourceIds, boardModel.dataSources); var specContent = { specVersion: specVersion, layout: layoutModel, widgets: layoutWidgets, dataSources: dataSources, dashboardID: '', errMsg: errMsg, nodeCopied: true }; if (errMsg) { specContent.nodeCopied = false; } // make this consistent with specContent.widgets specContent.nonMergedWidgets = nonMergedWidgets; if (selectedContent.getContainer()) { specContent.parentID = selectedContent.getContainer().getId(); } if (boardModel.drillThrough && typeof boardModel.drillThrough.getModels === 'function') { var drillThroughService = _this.dashboardApi.getDashboardCoreSvc('DrillThroughService'); // ALERT! WARNING! DANGER! (see above) specContent.drillThrough = drillThroughService.getDrillThroughByWidgetAndGroups(widgetModel); } if (boardModel.properties && boardModel.properties.customColors) { specContent.properties = { customColors: { colors: boardModel.properties.customColors.colors } }; } if (boardModel.fredIsRed) { specContent.fredIsRed = boardModel.fredIsRed; } // 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 (layoutModel.content && layoutModel.content.properties) { var contentProperties = Object.keys(layoutModel.content && layoutModel.content.properties); if (contentProperties.indexOf('explicitScale') !== -1) { delete specContent.layout.content.properties['explicitScale']; } } // Time Line model exists if (timelineEpisodes.length > 0) { specContent.episodes = timelineEpisodes; } // pageContext only needs to be added once if (specContents.length === 0) { var pageContext = _this._getPageContext(boardModel.pageContext); if (pageContext && pageContext.length) { specContent.pageContext = pageContext; // also add dashboard id, top level of the layout (needed to determine if pasting in the same DB) if (_this.controller.layoutController.topLayoutModel.id) { specContent.dashboardID = _this.controller.layoutController.topLayoutModel.id; } } } specContents.push(specContent); }); return specContents; }; /** * Gets all the filters applied on the widget * @param {Object} widgetModel - the widget associated inside the spec * @returns returns the widget obj adding the filters if any */ DashboardSpecHelper.prototype._addFilterToWidgetModel = function _addFilterToWidgetModel(widgetModel) { var content = this.dashboardApi.getCanvas().getContent(widgetModel.id); var legacyVisualization = content && content.getFeature('Visualization.legacy'); if (legacyVisualization) { var widgetModelClone = $.extend(true, {}, widgetModel); var portableFilters = legacyVisualization.getLegacyManagers().visFilterSupport.getAllFiltersAsLocalFiltersForPinning(); if (portableFilters) { widgetModelClone.localFilters = portableFilters; delete widgetModelClone.filters; } return widgetModelClone; } else { return widgetModel; } }; /** * Gets the spec content of a widget as JSON, it assumes that no pinning content is needed * @param {ContentAPI} content (Optional - if undefined, the selected content list will be used instead) * @returns {Object} Returns an object which contains the spec content represented as JSON, use property `spec` to access the spec JSON string */ DashboardSpecHelper.prototype.getContentsToJSONSpec = function getContentsToJSONSpec(content) { var JSONcontent = {}; var JSONSpec = void 0; var specContents = this.getContents(false, content); var assets = _.countBy(specContents, function (content) { return content.nodeCopied ? 'copied' : 'notCopied'; }); specContents = this._removeErrorsFromSpec(specContents, JSONcontent); if (specContents.length) { if (specContents[0].specVersion) { JSONcontent.specVersion = specContents[0].specVersion; } JSONcontent.layout = _.map(specContents, function (content) { return content.layout.toJSON(); }); JSONcontent.parentID = _.map(specContents, function (content) { return content.parentID; }); JSONcontent.pageContext = _.map(specContents[0].pageContext, function (content) { return content.toJSON(); }); JSONcontent.dashboardID = specContents[0].dashboardID; JSONcontent.widgets = _.chain(specContents).map(function (content) { return content.widgets; }).flatten().map(function (widget) { return widget.toJSON(); }).value(); JSONcontent.nonMergedWidgets = _.chain(specContents).map(function (content) { return content.nonMergedWidgets; }).flatten().map(function (widget) { return widget.toJSON(); }).value(); JSONcontent.dataSources = {}; JSONcontent.dataSources.version = specContents[0].dataSources.version; JSONcontent.dataSources.sources = []; var resultDrillThroughDefinitions = []; _.each(specContents, function (content) { _.each(content.dataSources.sources, function (source) { var idx = -1; // if the dataSource is already there, don't add it again if (JSONcontent.dataSources.sources.length > 0) { idx = JSONcontent.dataSources.sources.findIndex(function (ele) { return ele.id === source.id; }); } if (idx < 0) { var sourceJSON = source.toJSON(); // if the datasource is saved as an embedded module, the embedded module is saved // as an attachment to the original dashboard object in CM. Sharing the embedded module // can lead to side effects which was not intended from the original dashboard, which // leads to a stale datasource. // Remove the embeddedModuleId so that the target dashboard does not share the embedded module // with the source dashboard, but rather builds its own embedded module. if (sourceJSON && sourceJSON.shaping && sourceJSON.shaping.embeddedModuleId) { delete sourceJSON.shaping.embeddedModuleId; delete sourceJSON.shaping.embeddedModuleName; sourceJSON.shaping.embeddedModuleUpToDate = false; } JSONcontent.dataSources.sources.push(sourceJSON); } }); // Add drill through definitions from selected widget without duplicates. if (content.drillThrough && content.drillThrough.length) { content.drillThrough.forEach(function (drillDefinition) { var definitionExists = _.find(resultDrillThroughDefinitions, function (resultDefinition) { return resultDefinition.id === drillDefinition.id; }); if (!definitionExists) { resultDrillThroughDefinitions.push(drillDefinition.toJSON()); } }); } }); if (specContents[0].properties) { JSONcontent.properties = specContents[0].properties; } if (specContents[0].fredIsRed) { JSONcontent.fredIsRed = specContents[0].fredIsRed; } if (resultDrillThroughDefinitions.length) { JSONcontent.drillThrough = resultDrillThroughDefinitions; } var episodes = _.chain(specContents).map(function (content) { return content.episodes; }).flatten().filter(Boolean).map(function (episode) { return episode.toJSON(); }).value(); if (episodes.length) { JSONcontent.episodes = episodes; } } if (JSONcontent) { JSONSpec = JSON.stringify(JSONcontent, null, 4); } return { spec: JSONSpec, count: assets.copied, errMsg: JSONcontent.errMsg }; }; /** * Validates a dashboard JSON spec * @param {string} spec - spec to validate * @param {array} retErrors - (optional), if present, all errors are added to it and not shown in the Dashboard. * The object is {schema,schemaCondition,schemaKeyword,schemaDataPath,data,msg}; * @returns returns the JSON objects of the valid spec, otherwise null if validation fails */ DashboardSpecHelper.prototype.validateDashboardSpec = function validateDashboardSpec(spec, retErrors) { var _this2 = this; return Promise.resolve().then(function () { var defaultSchema = 'noSchema'; var defaultSchemaCondition = 'noSchemaCondition'; var defaultSchemaKeyword = 'noSchemaKeyword'; var defaultSchemaDataPath = 'noSchemaDataPath'; var defaultData = 'noData'; var defaultMsg = 'noMsg'; var errorObj = { 'schema': defaultSchema, 'schemaCondition': defaultSchemaCondition, 'schemaKeyword': defaultSchemaKeyword, 'schemaDataPath': defaultSchemaDataPath, 'data': defaultData, 'msg': defaultMsg }; var outSpec = void 0; try { outSpec = JSON.parse(spec); } catch (err) { var msg = 'Invalid JSON provided'; if (_this2.logger) { _this2.logger.error(msg, err, _this2); } if (retErrors !== undefined && retErrors !== null && _.isArray(retErrors)) { var errObj = Object.create(errorObj); errObj.data = err; errObj.msg = msg; retErrors.push(errObj); return null; } else { throw new Error('invalidJSONResponse'); } } // finds a widget function findWidget(id, type, widgetIds) { if (type === 'widget') { var widget = findObj(outSpec.widgets, id); if (!widget) { // if widget can not be found, save layout id widgetIds.push(id); } } } // recursively finds widgets function findWidgets(items, widgetIds) { _.each(items, function (item) { return item.items ? findWidgets(item.items, widgetIds) : findWidget(item.id, item.type, widgetIds); }); } var errors = []; // check for empty spec var specFragment = false; if (!outSpec || Object.keys(outSpec).length === undefined || Object.keys(outSpec).length === 0) { // signal error var _msg = 'Spec is empty'; if (retErrors !== undefined && retErrors !== null && _.isArray(retErrors)) { var _errObj = Object.create(errorObj); _errObj.msg = _msg; retErrors.push(_errObj); return null; } else { throw new Error(_msg); } } else if (Array.isArray(outSpec.layout)) { // spec fragment since the only other choice is an array specFragment = true; } // if in production, do not validate the spec against schemas if (!GlassUtil.isDevMode(_this2.glassContext)) { return outSpec; } return _this2._getAjv().then(function (ajv) { // use key/value pair to determine which schema to use with a spec key var shmaMappings = JSON.parse(SchemaMappings); // validate all of the spec, accumulate errors if any Object.keys(outSpec).forEach(function (specKey) { var keySchema = shmaMappings[specKey]; if (specFragment && specKey === 'layout') { keySchema = shmaMappings['layoutFragment']; } if (specFragment && specKey === 'widgets') { keySchema = shmaMappings['widgetsFragment']; } var specValue = outSpec[specKey]; if (keySchema) { var validate = ajv.getSchema(keySchema); if (validate) { if (!validate(specValue)) { // each call to validate destroys any previous errors so much save them validate.errors.forEach(function (error) { var errObj = Object.create(errorObj); errObj.schema = error.schemaPath; errObj.schemaCondition = error.parentSchema; errObj.schemaKeyword = error.keyword; errObj.schemaDataPath = error.dataPath; errObj.data = error.data; errObj.msg = error.message; errors.push(errObj); }); } } } // NOTE: for now leave this commented out since we don't have schemas for all spec keys // else { // generate error, invalid key // errors.push( { // 'msg': 'Spec Key: ' + specKey + ' does not exists in schema mapping definition' // }); // } }); // semantic checks // 1. check widget id in layout to make sure there is a widget in the spec for it. If any widget ids are returned, // then there is no widget object for it var widgetIds = []; if (outSpec.widgets) { if (outSpec.layout.items) { findWidgets(outSpec.layout.items, widgetIds); } else { findWidgets(outSpec.layout, widgetIds); } if (widgetIds.length) { var _errObj2 = Object.create(errorObj); _errObj2.schema = defaultSchema; _errObj2.schemaDataPath = defaultSchemaDataPath; _errObj2.schemaKeyword = defaultSchemaKeyword; _errObj2.data = defaultData; _errObj2.schemaCondition = 'semantic layout widget id check'; _errObj2.msg = 'No widget objects in the specification for layout ids ' + widgetIds.toString(); errors.push(_errObj2); } } if (errors.length) { outSpec = null; if (retErrors !== undefined && retErrors !== null && _.isArray(retErrors)) { retErrors.push(errors); } else { // log all errors if (_this2.logger) { _.each(errors, function (err) { var msg = void 0; if (err.msg !== defaultMsg) { msg = ' error: ' + err.msg; } if (err.data !== defaultData) { msg += ' data: ' + err.data; } if (err.schema !== defaultSchema) { msg += ' schema: ' + err.schema; } if (err.schemaCondition !== defaultSchemaCondition) { msg += ' schema defn: ' + err.schemaCondition.$id; } _this2.logger.error('schema validation error:', msg, _this2); }); } throw new Error('schema validation errors logged above'); } } return outSpec; }); }); }; DashboardSpecHelper.prototype._getAjv = function _getAjv() { var _this3 = this; if (this.ajv) { return Promise.resolve(this.ajv); } else { return new Promise(function (resolve, reject) { require(['ajv'], function (Ajv) { try { var shmaDefs = JSON.parse(SchemaDefs); // let shma = JSON.parse(Schema); var nameShma = JSON.parse(NameSchema); var layoutSchema = _this3._getLayoutSchema(); var layoutFragmentShma = JSON.parse(LayoutFragmentSchema); var themeShma = JSON.parse(ThemeSchema); var versionShma = JSON.parse(VersionSchema); var evtGrpsShma = JSON.parse(EventGroupsSchema); var drillThroughShma = JSON.parse(DrillThroughSchema); var propertiesShma = JSON.parse(PropertiesSchema); var pageContextShma = JSON.parse(PageContextSchema); var widgetsShma = JSON.parse(WidgetsSchema); var widgetsFragmentShma = JSON.parse(WidgetsFragmentSchema); var widgetsTemplateSchema = JSON.parse(WidgetsTemplateSchema); _this3.ajv = new Ajv({ allErrors: true, verbose: true, schemas: [shmaDefs, nameShma, layoutSchema, layoutFragmentShma, themeShma, versionShma, evtGrpsShma, drillThroughShma, propertiesShma, pageContextShma, widgetsShma, widgetsFragmentShma, widgetsTemplateSchema] }); resolve(_this3.ajv); } catch (err) { reject(err); } }, reject); }); } }; /** * get layout schema, it will add the registered types to the layoutType.enum * @returns the layout schema */ DashboardSpecHelper.prototype._getLayoutSchema = function _getLayoutSchema() { var layoutSchema = JSON.parse(LayoutSchema); var contentTypeRegistry = this.dashboardApi.getFeature('ContentTypeRegistry'); var layoutTypeEnum = layoutSchema.definitions.layoutType.enum; layoutSchema.definitions.layoutType.enum = layoutTypeEnum.concat(contentTypeRegistry.getRegisteredTypes()); return layoutSchema; }; /** * Creates an datasource spec object * @param {array} sourceIds - array of data source ids * @param {object} dataSources - array of datasources * @returns returns an object containing the datasource version and source objects */ DashboardSpecHelper.prototype._getDataSources = function _getDataSources(sourceIds, dataSources) { var copiedInfo = { version: '0.0', sources: [] }; if (dataSources && (typeof dataSources === 'undefined' ? 'undefined' : _typeof(dataSources)) === 'object') { copiedInfo.version = dataSources.version; var sources = copiedInfo.sources; sourceIds.forEach(function (id) { sources.push(dataSources.get('sources').get(id)); }); } return copiedInfo; }; /** * @param boardModel the boardModel * @param layoutWidgetId The layout id for the widget being processed. * @returns the timelineEpisode for widget which is attached to the pin for this layoutWidgetId. or null if there is none. * The model is returned from the board spec. * */ DashboardSpecHelper.prototype._getTimeLineEpisodeForPinning = function _getTimeLineEpisodeForPinning(boardModel, layoutWidgetId) { if (boardModel.timeline && typeof this.controller.boardModel.timeline.get === 'function') { return boardModel.timeline.episodes.get(layoutWidgetId); } return null; }; /** * Get current tab and global pageContexts * @param {array} pageContext - pageContext objects */ DashboardSpecHelper.prototype._getPageContext = function _getPageContext(pageContext) { if (pageContext && pageContext.getModels().length) { var pageCxt = []; var tabId = this.controller.layoutController.getCurrentSubViewId(); _.each(pageContext.getModels(), function (pc) { if (pc.getScope() === tabId || pc.getScope() === 'global') { pageCxt.push(pc); } }); return pageCxt; } }; /** * checks spec contents object for filter widget (denoted by errMsg being defined) and removes that element from the array * @param {array} specContents - content objects * @param {object} jsonContent - content to be converted to json object */ DashboardSpecHelper.prototype._removeErrorsFromSpec = function _removeErrorsFromSpec(specContents, jsonContent) { jsonContent.errMsg = _.first(_.compact(_.map(specContents, function (content) { return content.errMsg; }))); if (jsonContent.errMsg) { // keep the widget(s) that are not in error return _.filter(specContents, function (content) { return content.errMsg === undefined; }); } return specContents; }; return DashboardSpecHelper; }(); return DashboardSpecHelper; }); //# sourceMappingURL=DashboardSpecHelper.js.map