123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857 |
- '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 : '<NULL>') + ')';
- }
- }.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
|