'use strict'; /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2013, 2020 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ /* VisRenderSequence * The VisRenderSequence is a collection of methods that control the rendering sequence for widgets. * Rendering involves performing a sequence of 'steps' to build a state that is complete enough to render. * Once the requirements to render are met (control loaded, spec loaded, data loaded etc)., the view's render method is called. * */ define(['underscore', './VisRenderState', './VisRenderTrace', '../../DynamicFileLoader', '../../widgets/livewidget/util/VisUtil', '../../visualizations/vipr/VIPRLibraries', '../../lib/@waca/core-client/js/core-client/ui/core/Events', '../../lib/@waca/dashboard-common/dist/api/Error'], function (_, VisRenderState, VisRenderTrace, DynamicFileLoader, VisUtil, VIPRLibraries, Events, APIError) { 'use strict'; /** * Default sequence of task definitions * dependencies: array of dependencies * modulePath: path to the task module */ var DEFAULT_SEQUENCE = [{ id: 'visView', modulePath: './sequence/VisViewTask' }, { id: 'visControl', dependencies: ['visView'], modulePath: './sequence/VisControlTask' }, { id: 'visSpec', dependencies: ['visControl'], modulePath: './sequence/VisSpecTask' }, { id: 'data', dependencies: ['visSpec'], modulePath: './sequence/DataTask' }, { id: 'resultsdatareader', dependencies: ['data'], modulePath: './sequence/ResultsDataReaderTask' }, { id: 'highlighter', dependencies: ['resultsdatareader'], modulePath: './sequence/HighlighterTask' }, { id: 'setData', dependencies: ['resultsdatareader'], modulePath: './sequence/SetDataTask' }, { id: 'render', dependencies: ['highlighter'], modulePath: './sequence/RenderTask' }]; var BASE_PATH = 'dashboard-analytics/visualizations/renderer/'; var ERR_RENDER_ABORTED_MSG = 'widget render aborted'; var VisRenderSequence = Events.extend({ init: function init(attributes) { VisRenderSequence.inherited('init', this, [attributes]); this.logger = attributes.ownerWidget.logger; this.ownerWidget = attributes.ownerWidget; this.dashboardApi = attributes.ownerWidget.getDashboardApi(); this.content = this.ownerWidget.content; this.visAPI = attributes.visModel; //An object that stores the state of the render. this._renderState = new VisRenderState(); this._sequence = null; this._trace = new VisRenderTrace(this._renderState); this.widgetExtraStepsIdentifier = ''; this._renderId = -1; }, remove: function remove() { this._getRenderState().remove(); this._renderState = null; }, /** * Check whether the instance is still active or not * * @return {boolean} TRUE if the instance is active and FALSE if not */ isActive: function isActive() { return !!this._renderState; }, /** * This api is called when a visualization is first rendered or the renderer changes and everything has to be redone. * (or when the renderer changes). In this case, everything needs to be * reloaded. When only some parts of the render state are affected (eg, a data change, call reRender). * @param options (optional) - by default, render() implies a full, initial render. * If options are specified, render works equivalently to reRender with the same options. * The difference is in the default behaviour. With no options specified, reRender does not do a full render. */ render: function render(options) { if (this.logger) { this.logger.debug('Data widget render start', this.visAPI); } options = options || { refreshAll: true, initial: true }; return this.reRender(options); }, _getRenderState: function _getRenderState() { if (this._renderState) { return this._renderState; } else { throw new Error(ERR_RENDER_ABORTED_MSG); } }, /** * check if the canvas contentApi and renderSequence.internal feature is available; * for instance the contentApi is not available in the standalone widget, which is used in the conversation panel * the content is always defined but the features may not when adding a new widget * TODO fix feature lifecycle to make both the canvasAPI and contentAPI available * @return {boolean} true if the feature is available, false otherwise */ _isRenderSequenceAPIReady: function _isRenderSequenceAPIReady() { var isReady = typeof this._renderAPI !== 'undefined'; if (isReady === false) { // Prepare and register render engine for RenderSequenceFeature this._renderAPI = this.content.getFeature('RenderSequence.internal'); isReady = typeof this._renderAPI !== 'undefined'; if (isReady) { this._renderAPI.registerRenderEngine(this); } } return isReady; }, /** * gets the task processing methods based on the availability of the renderSequence.internal feature * do not set on this as multiple sequences can start concurrently * Important: the renderSequence is used for the data task only to start with * TODO remove this method by enabling the RenderSequence.internal for all tasks * @return {Object} with the 3 methods bound to this */ _getTaskProcessor: function _getTaskProcessor() { var taskProcessor = {}; if (this._isRenderSequenceAPIReady() === true) { taskProcessor.startTaskExecution = function (task, renderContext) { if (task.id === 'data') { return this._renderAPI.startTaskExecution({ task: task, renderContext: renderContext }); } else { return task.instance.process(renderContext); } }.bind(this); taskProcessor.resolveTaskExecution = function (taskExecution, task, renderContext) { if (task.id === 'data') { this._renderAPI.resolveTaskExecution({ taskExecution: taskExecution, task: task, renderContext: renderContext }); } else { taskExecution.resolve(); } }.bind(this); taskProcessor.rejectTaskExecution = function (taskExecution, error, task, renderContext) { if (task.id === 'data') { this._renderAPI.rejectTaskExecution({ taskExecution: taskExecution, task: task, renderContext: renderContext, error: error }); } else { taskExecution.reject(error); } }.bind(this); } else { taskProcessor.startTaskExecution = function (task, renderContext) { return task.instance.process(renderContext); }.bind(this); taskProcessor.resolveTaskExecution = function (taskExecution) { taskExecution.resolve(); }.bind(this); taskProcessor.rejectTaskExecution = function (taskExecution, error) { taskExecution.reject(error); }.bind(this); } return taskProcessor; }, /** * This api is called after the first render to 'update the rendering'. * Options are passed in to define what pieces of the Rendering need to be refreshed * Examples: * a resize would use pass no options <= which denotes reRender without refreshing any data. * a filter change would use options { refresh: { data: true }}, * a visualization type change where the renderer type stays the same would use { refresh: { visSpec: true } } * Initial render (or changing renderers) would pass { refreshAll: true } meaning all data needs to be loaded. */ reRender: function reRender(options) { var _this = this, _arguments = arguments; return VisUtil.validateVisDefinition(this.content, this.dashboardApi, { visId: this.ownerWidget.model.visId }).then(function (isValid) { if (isValid || options && options.initial) { var runRenderId = ++_this._renderId; options = options || { refresh: {} }; options.renderId = runRenderId; return _this._render(options).then(function (renderContext) { // only triggers 'widget:rerendered' when the change is data related if (options.refreshAll || options.refresh && options.refresh.data) { // TODO: remove this event on the dashboardAPI _this.dashboardApi.triggerDashboardEvent('widget:rerendered', { sender: _this.ownerWidget.id, refreshAll: options.refreshAll }); } _this._getRenderState().cleanRenderContextPool(); return renderContext; }).catch(function (error) { if (_this.isActive()) { // Error object may contain: // 1. Generic string 'message' // 2. Resource 'msg' identifier var errorMessage = error && (error.message || error.msg) || 'dwErrorRenderingVisualization'; var state = _this.content.getFeature('state'); var err = state.getError(); var dataSourceName = _this.visAPI && _this.visAPI.getModule() && _this.visAPI.getModule().getSourceName(); if (!dataSourceName) { if (errorMessage === 'dwErrorMissingDataset') { dataSourceName = err && err._params && err._params.datasetName; } else { dataSourceName = ' '; } } var isLastRun = _this.isLastRun({ id: runRenderId }); var knownErrors = [ERR_RENDER_ABORTED_MSG, // When we have multiple data queries and a newer request resolves before an older one. // The older one is marked as stale and throws this error. 'dwErrorStaleRequest', 'unSupportedPromptType', 'cancelPromptDialog']; var throwableErrors = [ // Explore will catch this error to decide rendering static image for preview and not prompting user 'promptingIsDisabled']; if (throwableErrors.indexOf(errorMessage) > -1) { throw error; } if (knownErrors.indexOf(errorMessage) === -1) { var errorInfo = void 0; if (error && error.errorInfo) { errorInfo = error.errorInfo; } else if (err && err.getParams) { var params = err.getParams(); errorInfo = params ? params.errorInfo : null; } if (isLastRun) { _this.showError(errorMessage, { 'datasetName': dataSourceName, errorInfo: errorInfo }); } //Do not log custom vis loading error to reduce the noise in console if (!errorInfo || errorInfo.errorCode !== VIPRLibraries.LOAD_DEFINITION_ERROR) { _this.logger.error('An error occurred while re-rendering test', _arguments, _this.visAPI, error); } } else { _this.logger.warn(errorMessage, { datasetName: dataSourceName }); } } else { // in case the render is aborted mid-render, log as debug but don't throw. // Treat as normal exception _this.logger.debug('An error occurred while re-rendering', _arguments, _this.visAPI); } }); } }); }, /** * Called during setDefinition if the renderer changes (e.g. from RaveView to GridView) * In this case, we need to load a new view/control and remove the old view from rendering etc. * */ onChangeRenderer: function onChangeRenderer(event) { var sender = event && event.sender; this._getRenderState().lastSizeRendered = null; this._getRenderState().reloadAllRenderSteps(); //Clear the loaded steps in the render sequence. if (sender !== 'UndoRedoController') { var undoRedoTransactionId = event && event.data && event.data.undoRedoTransactionId; var options = { undoRedoTransactionId: undoRedoTransactionId, transactionToken: event && event.data && event.data.transactionToken }; this.ownerWidget.setPreferredSize(this.visAPI.getDefinition().preferredSize, options); } return this.render({ refreshAll: true, initial: false }); }, /** * Gets the last rendered size from the current render state. */ getLastSizeRendered: function getLastSizeRendered() { return this._renderState ? this._renderState.lastSizeRendered : null; }, /** * @returns true if the initial render is complete. */ firstRenderComplete: function firstRenderComplete() { return this._getRenderState().firstRenderComplete; }, /** * Perform all steps of the render sequence that apply to a given renderContext. * When done, if the renderState is complete, call the view to render the visualization. * If not, resolve anyway....the render will be done by completing the steps for a * subsequent render context. * * @param renderContext - information collected by a particular call to render or reRender (such as the view, control, data etc). */ _render: function _render(options) { var contentStateError = this.content && this.content.getFeature('state').getError(); var isMissingDataSource = contentStateError && contentStateError.getMessage() === 'dwErrorMissingDataset'; if (isMissingDataSource || !options.refresh && !options.refreshAll) { var widgetError = this.ownerWidget && this.ownerWidget.getAPI().getVisApi().getInvalidReason(); var _contentStateError = this.content && this.content.getFeature('state').getError(); if (widgetError || _contentStateError) { // Cannot render this visualization return Promise.reject(new Error(widgetError && widgetError.msg || _contentStateError && _contentStateError.getMessage())); } } this.ownerWidget.renderStart(options); // The main steps in the renderSequence. // Depending on the view, some steps may not apply and simply resolve immediately. // A particular renderContext might just be responsible for refreshing data. In this case, // other steps will resolve immediately and pass the context through. if (this._isRenderSequenceAPIReady()) { return this._renderAPI.triggerRenderSequence(options); } else { return this.process(options); } }, _initializeStep: function _initializeStep(step) { this._getRenderState().initCurrentContext(step); }, /** * A "step" in the render sequence is complete IF * this renderContext is responsible for this step and has initialized it * OR if this renderContext is not responsible for this step * (eg: a renderContext when a filter changes is only responsible for fetching data but * the initial render is responsible for fetching everything...unless a data change happens fast). * * @param renderContext - information collected by a particular call to render or reRender (such as the view, control, data etc). * @param stepName The step (one of visView, visControl, data, visSpec, highlighter) */ _isStepComplete: function _isStepComplete(renderContext, stepName) { var contextForThisStep = this._getRenderState().getCurrentContext(stepName); var contextMatch = renderContext === contextForThisStep; return !contextMatch || contextMatch && renderContext[stepName]; }, /** * This function is executed when a step is completed to initialize * the appropriate member of renderContext * @param renderContext - information collected by a particular call to render or reRender (such as the view, control, data etc). * @param step - one of the steps in the render process like 'visView', 'visControl' or 'data' * @param value - the value to assign the render context step member to. * @returns true if the current context step corresponds to this renderInfo, false otherwise. */ _completeStep: function _completeStep(renderContext, step, value) { renderContext[step] = value; var currentStep = this._renderState ? this._renderState.getCurrentContext(step) : null; var stepMatch = currentStep && currentStep.id === renderContext.id; return !!currentStep && stepMatch; }, /** * Helper for it test. */ getVisControl: function getVisControl() { return this._getRenderState().getCurrentContextData('visControl'); }, getVisSpec: function getVisSpec() { return this._getRenderState().getCurrentContextData('visSpec'); }, /** * Create a render sequence of task definitions with the custom tasks * when the custom tasks are omitted, default sequence is returned. * Custom tasks will overwrite default tasks if they have the same id. * @param sequence - array of custom task definitions (optional) * @returns array of task definitions */ createRenderSequence: function createRenderSequence() { var _this2 = this; var sequence = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; // Make sure to clone DEFAULT_SEQUENCE this._sequence = DEFAULT_SEQUENCE.concat([]); var newSequences = Array.isArray(sequence) ? sequence : [sequence]; newSequences.forEach(function (newSequence) { var index = _this2._sequence.findIndex(function (defaultSequence) { return defaultSequence.id === newSequence.id; }); if (index === -1) { _this2._sequence.push(newSequence); } else { _this2._sequence[index] = newSequence; } }); this._createDependenciesFromBlockers(); // Validate the dependencies for each step this._sequence.forEach(function (step) { _this2._validateDependencies(step.id); }); return this._sequence; }, _createDependenciesFromBlockers: function _createDependenciesFromBlockers() { var _this3 = this; this._sequence.forEach(function (step) { if (step.blocks) { step.blocks.forEach(function (id) { var blockedTask = _this3._getSequenceTask(id); if (blockedTask) { var dependencies = blockedTask.dependencies || []; if (dependencies.indexOf(step.id) === -1) { dependencies.push(step.id); } } }); delete step.blocks; } }); }, _getSequenceTask: function _getSequenceTask(id) { return this._sequence.find(function (item) { return item.id === id; }); }, _validateDependencies: function _validateDependencies(taskId, currentDeps) { var _this4 = this; var dependencies = currentDeps || []; if (dependencies.indexOf(taskId) !== -1) { throw new Error('The task with id "' + taskId + '" has a a circular dependency: ' + currentDeps); } dependencies.push(taskId); var task = this._getSequenceTask(taskId); if (task.dependencies) { task.dependencies.forEach(function (depItem) { if (!_this4._getSequenceTask(depItem)) { throw new Error('The task with id "' + taskId + '" has a missing dependency "' + depItem + '"'); } var clonedDepArray = dependencies.concat([]); _this4._validateDependencies(depItem, clonedDepArray); }); } }, /* * 'For each step in the render sequence, build a map entry that includes the following members: * "data" : { * id: {step.id}, * resolve: {Execution reolve function}, * reject: {Execution reject function} * whenStepIsComplete: { thisStepIsCompletePromise }, * whenDepsAreComplete: [ dependencyStepIsCompletePromises ] * } * */ _createExecutionState: function _createExecutionState(renderContext) { var _this5 = this; // Hold on to the previous execution state. // we might need to coordinate between the execution of steps between different runs // Render#1 (full render): dataTask1 --> renderTask1 // Render#2 (partial resize render): renderTask2 // In render#2, renderTask2 should also be blocked until dataTask1 of Render#1 is complete var previousExecutionStateMap = null; if (this._isExecuting) { previousExecutionStateMap = this.executionStateMap; } this.executionStateMap = {}; // Attach the whenStepIsComplete promise this._sequence.forEach(function (step) { var execution = { renderId: renderContext.id, id: step.id }; _this5.executionStateMap[step.id] = execution; execution.whenStepIsComplete = new Promise(function (resolve, reject) { execution.resolve = resolve; execution.reject = reject; }); // eslint-disable-next-line execution.whenStepIsComplete.catch(function (error) { //defined to avoid bluebird unhandled rejection warning message }); }); // Attach the whenDepsAreComplete promise this._sequence.forEach(function (step) { var dependentStepCompletePromises = []; if (step.dependencies) { step.dependencies.forEach(function (dep) { dependentStepCompletePromises.push(_this5.executionStateMap[dep].whenStepIsComplete); _this5._trace.taskDependency(step, renderContext, dep); // if a blocking step is not part of the render sequence, // check if it is in the previous execution state map and wait for it. var previousExecutionStepState = previousExecutionStateMap && previousExecutionStateMap[dep]; if (previousExecutionStepState && !_this5._isPartOfTheCurrentRun(renderContext, dep)) { dependentStepCompletePromises.push(previousExecutionStepState.whenStepIsComplete); _this5._trace.taskDependency(step, renderContext, { renderId: previousExecutionStepState.renderId, id: dep }); } }); } _this5.executionStateMap[step.id].whenDepsAreComplete = Promise.all(dependentStepCompletePromises); }); }, getTaskExecutionState: function getTaskExecutionState(id) { return this.executionStateMap[id]; }, /** * checks if the passed renderContext corresponds to the latest run * it is different from @see _isPartOfTheCurrentRun which checks if the * renderId associated to a step corresponds to the one associated to the current context * @param {number} renderContext - renderContext of a sequence run * @returns {boolean} true if it corresponds to the last run, false otherwise */ isLastRun: function isLastRun(renderContext) { return renderContext.id === this._renderId; }, /** * * checks if the renderId defined in the passed renderContext for the specified step corresponds to the one * stored in the current context * @param {Object} renderContext * @param {String} stepName - name of the steps being processed * @returns {boolean} true if it corresponds to the */ _isPartOfTheCurrentRun: function _isPartOfTheCurrentRun(renderContext, stepName) { var currentStepContext = this._getRenderState().getCurrentContext(stepName); return currentStepContext && currentStepContext.id === renderContext.id; }, /** * Recursively collect all the dependent steps (including dependents of dependents) among the sequence * @param {string} id - id of step * @param {object[]} sequence - sequence of all steps */ _getAllDependencies: function _getAllDependencies(id, sequence) { var _this6 = this; var dependencies = {}; _.each(sequence, function (step) { if (step.dependencies && step.dependencies.indexOf(id) > -1) { dependencies[step.id] = step; _.extend(dependencies, _this6._getAllDependencies(step.id, sequence)); } }); return dependencies; }, /** * Apply the refresh flag to all dependent render sequence steps * @param {object[]} sequence - sequence of render sequence steps * @param {object} options - render options * @return render options including refresh on all dependents */ _applyRefreshDependencies: function _applyRefreshDependencies(sequence, options) { var _this7 = this; if (options.refresh) { // walk through each steps to be refreshed _.each(options.refresh, function (refreshVal, id) { if (refreshVal) { _.each(_this7._getAllDependencies(id, sequence), function (step) { options.refresh[step.id] = true; }); } }); } return options; }, /** * Create an identifier that can be used of the step list is modified * For now, the implementation is only using the step ids. */ _getWidgetStepsIdentifier: function _getWidgetStepsIdentifier(steps) { return steps.map(function (step) { return step.id; }).toString(); }, /** * Make sure that the sequence defintion is created and that it reflects * the latest enabled widget features. * */ _ensureSequenceUpToDate: function _ensureSequenceUpToDate() { // this list of extra render steps is for babckward compatibility // and to support the legacy widget features. // Once all features move to the official content feature api and use the RenderStepProviderAPI, // then this will no longer be needed. var widgetExtraSteps = this.ownerWidget.getExtraRenderSequenceSteps(); // Get the extra steps registered by the features providers if (this._isRenderSequenceAPIReady()) { widgetExtraSteps = widgetExtraSteps.concat(this._renderAPI.getProvidersRenderStepList()); } if (this.widgetExtraStepsIdentifier !== this._getWidgetStepsIdentifier(widgetExtraSteps)) { // The widget render steps have changed. We need to recreate the sequence this._sequence = null; } if (!this._sequence) { this.createRenderSequence(widgetExtraSteps); // Keep track of the widget steps in order to detect when they change this.widgetExtraStepsIdentifier = this._getWidgetStepsIdentifier(widgetExtraSteps); } }, /** * Process the sequence of tasks * @param renderContext - the render context * @returns a promise */ process: function process(options) { var _this8 = this; this._ensureSequenceUpToDate(); return this._loadTasks(this._sequence).then(function (tasks) { return _this8._preProcessDataOptions(options).then(function (options) { options = _this8._applyRefreshDependencies(_this8._sequence, options); var renderContext = _this8._renderState.addRenderContext(options); _this8._logRenderContext('_render', renderContext); _this8._trace.startRender(tasks, renderContext, options); return _this8._processTasks(tasks, renderContext); }); }); }, /** * Prior to adding the renderContext, check for the 'dataIfQueryChanged' option. * If specified, check if the query changed and include/exclude the data step. * @param options an options object (or null) * @returns a promise with the original options (or the updated options if applicable). */ _preProcessDataOptions: function _preProcessDataOptions(options) { if (options && options.refresh && options.refresh.dataIfQueryChanged) { delete options.refresh.dataIfQueryChanged; return this._queryChanged().then(function (changed) { if (changed.isRenderNeeded) { options.refresh.data = true; } else { delete options.refresh.data; } return options; }); } return Promise.resolve(options); }, _queryChanged: function _queryChanged() { // TODO Wrap to async result when use new query API. Need to be cleaned once switch to query api. if (this.ownerWidget.useNewQueryApi()) { var internalQueryExecution = this.content.getFeature('DataQueryExecution.internal'); return Promise.resolve({ isRenderNeeded: internalQueryExecution.queryChanged() }); } else { return this.visAPI.queryChanged(); } }, /* * Execute a list of tasks. * This function will create a new execution state, and will execute all * the independent tasks in parallel and only synchronize the dependent tasks. * */ _processTasks: function _processTasks(tasks, renderContext) { var _this9 = this; this._isExecuting = true; // create the execution promises that will coordinate the steps dependency execution this._createExecutionState(renderContext); var taskProcessor = this._getTaskProcessor(); // TODO temporarily add useAPI flag to renderContext // Final cleaning should search useAPI and clean all related code. renderContext.useAPI = this.ownerWidget.useNewQueryApi(); var processTaskPromises = []; // let renderSequenceFeature = this.contentApi.getFeature('renderSequence.internal'); tasks.forEach(function (task) { var taskExecution = _this9.getTaskExecutionState(task.id); processTaskPromises.push(taskExecution.whenDepsAreComplete.then(function () { _this9._trace.startTask(task, renderContext); try { return taskProcessor.startTaskExecution(task, renderContext).then(function () { _this9._trace.endTask(task, renderContext, 'SUCCEEDED'); taskProcessor.resolveTaskExecution(taskExecution, task, renderContext); }).catch(function (err) { _this9._trace.endTask(task, renderContext, 'FAILED', err); taskProcessor.rejectTaskExecution(taskExecution, err, task, renderContext); throw err; }); } catch (err) { _this9._trace.endTask(task, renderContext, 'FAILED', err); taskProcessor.rejectTaskExecution(taskExecution, err, task, renderContext); throw err; } }, function (err) { _this9._trace.endTask(task, renderContext, 'DEPENDENCY_FAILED', err); taskExecution.reject(err); throw err; })); }); return Promise.all(processTaskPromises).then(function () { _this9._isExecuting = false; return renderContext; }); }, _resolvePath: function _resolvePath(path) { if (!path || path.length === 0) { return null; } var resolved = path; var relativeIndex = path.indexOf('./'); if (relativeIndex > -1) { // @todo support for multi-level relative paths resolved = BASE_PATH + path.substr(relativeIndex + 2); } return resolved; }, _getSequenceModulePaths: function _getSequenceModulePaths(sequence) { var _this10 = this; return _.chain(sequence) // extract the resolved path .map(function (_ref) { var modulePath = _ref.modulePath; return _this10._resolvePath(modulePath); }).value(); }, _loadTasks: function _loadTasks(sequence) { var _this11 = this; var paths = this._getSequenceModulePaths(sequence); return DynamicFileLoader.load(paths).then(function (Modules) { return _.map(sequence, function (step, index) { var Module = step.module || Modules[index]; return { instance: new Module({ id: step.id, owner: _this11, ownerWidget: _this11.ownerWidget, logger: _this11.logger, visAPI: _this11.visAPI, dashboardApi: _this11.dashboardApi, content: _this11.content }, step.moduleOptions), id: step.id }; }); }); }, _logRenderContext: function _logRenderContext(stage, renderContext) { var renderTimeInfo = 'render sequence stage:' + stage + ' {rendering ' + this.visAPI.id + ':'; _.each(_.keys(renderContext), function (key) { if (key !== 'id') { var c = this._getRenderState().getCurrentContext(key); renderTimeInfo += key + '(' + (c ? c.id : '') + ')'; } }.bind(this)); var model = this.ownerWidget.model ? this.ownerWidget.model.toJSON() : 'NULL'; this.logger.debug(renderTimeInfo + ', id=' + renderContext.id + '}', model); }, showError: function showError(msg, params, type) { var state = this.content.getFeature('state.internal'); if (state) { var error = new APIError({ 'msg': msg, 'params': params }, { 'type': type }); state.setError(error); } }, enableTrace: function enableTrace() { var enable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; return this._trace.enable(enable); }, clearTrace: function clearTrace() { return this._trace.clear(); }, getTrace: function getTrace() { return this._trace.getTrace(); } }); return VisRenderSequence; }); //# sourceMappingURL=VisRenderSequence.js.map