VisRenderSequence.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: Dashboard
  6. *| (C) Copyright IBM Corp. 2013, 2020
  7. *|
  8. *| US Government Users Restricted Rights - Use, duplication or disclosure
  9. *| restricted by GSA ADP Schedule Contract with IBM Corp.
  10. *+------------------------------------------------------------------------+
  11. */
  12. /* VisRenderSequence
  13. * The VisRenderSequence is a collection of methods that control the rendering sequence for widgets.
  14. * Rendering involves performing a sequence of 'steps' to build a state that is complete enough to render.
  15. * Once the requirements to render are met (control loaded, spec loaded, data loaded etc)., the view's render method is called.
  16. *
  17. */
  18. 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) {
  19. 'use strict';
  20. /**
  21. * Default sequence of task definitions
  22. * dependencies: array of dependencies
  23. * modulePath: path to the task module
  24. */
  25. var DEFAULT_SEQUENCE = [{
  26. id: 'visView',
  27. modulePath: './sequence/VisViewTask'
  28. }, {
  29. id: 'visControl',
  30. dependencies: ['visView'],
  31. modulePath: './sequence/VisControlTask'
  32. }, {
  33. id: 'visSpec',
  34. dependencies: ['visControl'],
  35. modulePath: './sequence/VisSpecTask'
  36. }, {
  37. id: 'data',
  38. dependencies: ['visSpec'],
  39. modulePath: './sequence/DataTask'
  40. }, {
  41. id: 'resultsdatareader',
  42. dependencies: ['data'],
  43. modulePath: './sequence/ResultsDataReaderTask'
  44. }, {
  45. id: 'highlighter',
  46. dependencies: ['resultsdatareader'],
  47. modulePath: './sequence/HighlighterTask'
  48. }, {
  49. id: 'setData',
  50. dependencies: ['resultsdatareader'],
  51. modulePath: './sequence/SetDataTask'
  52. }, {
  53. id: 'render',
  54. dependencies: ['highlighter'],
  55. modulePath: './sequence/RenderTask'
  56. }];
  57. var BASE_PATH = 'dashboard-analytics/visualizations/renderer/';
  58. var ERR_RENDER_ABORTED_MSG = 'widget render aborted';
  59. var VisRenderSequence = Events.extend({
  60. init: function init(attributes) {
  61. VisRenderSequence.inherited('init', this, [attributes]);
  62. this.logger = attributes.ownerWidget.logger;
  63. this.ownerWidget = attributes.ownerWidget;
  64. this.dashboardApi = attributes.ownerWidget.getDashboardApi();
  65. this.content = this.ownerWidget.content;
  66. this.visAPI = attributes.visModel;
  67. //An object that stores the state of the render.
  68. this._renderState = new VisRenderState();
  69. this._sequence = null;
  70. this._trace = new VisRenderTrace(this._renderState);
  71. this.widgetExtraStepsIdentifier = '';
  72. this._renderId = -1;
  73. },
  74. remove: function remove() {
  75. this._getRenderState().remove();
  76. this._renderState = null;
  77. },
  78. /**
  79. * Check whether the instance is still active or not
  80. *
  81. * @return {boolean} TRUE if the instance is active and FALSE if not
  82. */
  83. isActive: function isActive() {
  84. return !!this._renderState;
  85. },
  86. /**
  87. * This api is called when a visualization is first rendered or the renderer changes and everything has to be redone.
  88. * (or when the renderer changes). In this case, everything needs to be
  89. * reloaded. When only some parts of the render state are affected (eg, a data change, call reRender).
  90. * @param options (optional) - by default, render() implies a full, initial render.
  91. * If options are specified, render works equivalently to reRender with the same options.
  92. * The difference is in the default behaviour. With no options specified, reRender does not do a full render.
  93. */
  94. render: function render(options) {
  95. if (this.logger) {
  96. this.logger.debug('Data widget render start', this.visAPI);
  97. }
  98. options = options || { refreshAll: true, initial: true };
  99. return this.reRender(options);
  100. },
  101. _getRenderState: function _getRenderState() {
  102. if (this._renderState) {
  103. return this._renderState;
  104. } else {
  105. throw new Error(ERR_RENDER_ABORTED_MSG);
  106. }
  107. },
  108. /**
  109. * check if the canvas contentApi and renderSequence.internal feature is available;
  110. * for instance the contentApi is not available in the standalone widget, which is used in the conversation panel
  111. * the content is always defined but the features may not when adding a new widget
  112. * TODO fix feature lifecycle to make both the canvasAPI and contentAPI available
  113. * @return {boolean} true if the feature is available, false otherwise
  114. */
  115. _isRenderSequenceAPIReady: function _isRenderSequenceAPIReady() {
  116. var isReady = typeof this._renderAPI !== 'undefined';
  117. if (isReady === false) {
  118. // Prepare and register render engine for RenderSequenceFeature
  119. this._renderAPI = this.content.getFeature('RenderSequence.internal');
  120. isReady = typeof this._renderAPI !== 'undefined';
  121. if (isReady) {
  122. this._renderAPI.registerRenderEngine(this);
  123. }
  124. }
  125. return isReady;
  126. },
  127. /**
  128. * gets the task processing methods based on the availability of the renderSequence.internal feature
  129. * do not set on this as multiple sequences can start concurrently
  130. * Important: the renderSequence is used for the data task only to start with
  131. * TODO remove this method by enabling the RenderSequence.internal for all tasks
  132. * @return {Object} with the 3 methods bound to this
  133. */
  134. _getTaskProcessor: function _getTaskProcessor() {
  135. var taskProcessor = {};
  136. if (this._isRenderSequenceAPIReady() === true) {
  137. taskProcessor.startTaskExecution = function (task, renderContext) {
  138. if (task.id === 'data') {
  139. return this._renderAPI.startTaskExecution({ task: task, renderContext: renderContext });
  140. } else {
  141. return task.instance.process(renderContext);
  142. }
  143. }.bind(this);
  144. taskProcessor.resolveTaskExecution = function (taskExecution, task, renderContext) {
  145. if (task.id === 'data') {
  146. this._renderAPI.resolveTaskExecution({ taskExecution: taskExecution, task: task, renderContext: renderContext });
  147. } else {
  148. taskExecution.resolve();
  149. }
  150. }.bind(this);
  151. taskProcessor.rejectTaskExecution = function (taskExecution, error, task, renderContext) {
  152. if (task.id === 'data') {
  153. this._renderAPI.rejectTaskExecution({ taskExecution: taskExecution, task: task, renderContext: renderContext, error: error });
  154. } else {
  155. taskExecution.reject(error);
  156. }
  157. }.bind(this);
  158. } else {
  159. taskProcessor.startTaskExecution = function (task, renderContext) {
  160. return task.instance.process(renderContext);
  161. }.bind(this);
  162. taskProcessor.resolveTaskExecution = function (taskExecution) {
  163. taskExecution.resolve();
  164. }.bind(this);
  165. taskProcessor.rejectTaskExecution = function (taskExecution, error) {
  166. taskExecution.reject(error);
  167. }.bind(this);
  168. }
  169. return taskProcessor;
  170. },
  171. /**
  172. * This api is called after the first render to 'update the rendering'.
  173. * Options are passed in to define what pieces of the Rendering need to be refreshed
  174. * Examples:
  175. * a resize would use pass no options <= which denotes reRender without refreshing any data.
  176. * a filter change would use options { refresh: { data: true }},
  177. * a visualization type change where the renderer type stays the same would use { refresh: { visSpec: true } }
  178. * Initial render (or changing renderers) would pass { refreshAll: true } meaning all data needs to be loaded.
  179. */
  180. reRender: function reRender(options) {
  181. var _this = this,
  182. _arguments = arguments;
  183. return VisUtil.validateVisDefinition(this.content, this.dashboardApi, { visId: this.ownerWidget.model.visId }).then(function (isValid) {
  184. if (isValid || options && options.initial) {
  185. var runRenderId = ++_this._renderId;
  186. options = options || {
  187. refresh: {}
  188. };
  189. options.renderId = runRenderId;
  190. return _this._render(options).then(function (renderContext) {
  191. // only triggers 'widget:rerendered' when the change is data related
  192. if (options.refreshAll || options.refresh && options.refresh.data) {
  193. // TODO: remove this event on the dashboardAPI
  194. _this.dashboardApi.triggerDashboardEvent('widget:rerendered', {
  195. sender: _this.ownerWidget.id,
  196. refreshAll: options.refreshAll
  197. });
  198. }
  199. _this._getRenderState().cleanRenderContextPool();
  200. return renderContext;
  201. }).catch(function (error) {
  202. if (_this.isActive()) {
  203. // Error object may contain:
  204. // 1. Generic string 'message'
  205. // 2. Resource 'msg' identifier
  206. var errorMessage = error && (error.message || error.msg) || 'dwErrorRenderingVisualization';
  207. var state = _this.content.getFeature('state');
  208. var err = state.getError();
  209. var dataSourceName = _this.visAPI && _this.visAPI.getModule() && _this.visAPI.getModule().getSourceName();
  210. if (!dataSourceName) {
  211. if (errorMessage === 'dwErrorMissingDataset') {
  212. dataSourceName = err && err._params && err._params.datasetName;
  213. } else {
  214. dataSourceName = ' ';
  215. }
  216. }
  217. var isLastRun = _this.isLastRun({ id: runRenderId });
  218. var knownErrors = [ERR_RENDER_ABORTED_MSG,
  219. // When we have multiple data queries and a newer request resolves before an older one.
  220. // The older one is marked as stale and throws this error.
  221. 'dwErrorStaleRequest', 'unSupportedPromptType', 'cancelPromptDialog'];
  222. var throwableErrors = [
  223. // Explore will catch this error to decide rendering static image for preview and not prompting user
  224. 'promptingIsDisabled'];
  225. if (throwableErrors.indexOf(errorMessage) > -1) {
  226. throw error;
  227. }
  228. if (knownErrors.indexOf(errorMessage) === -1) {
  229. var errorInfo = void 0;
  230. if (error && error.errorInfo) {
  231. errorInfo = error.errorInfo;
  232. } else if (err && err.getParams) {
  233. var params = err.getParams();
  234. errorInfo = params ? params.errorInfo : null;
  235. }
  236. if (isLastRun) {
  237. _this.showError(errorMessage, {
  238. 'datasetName': dataSourceName,
  239. errorInfo: errorInfo
  240. });
  241. }
  242. //Do not log custom vis loading error to reduce the noise in console
  243. if (!errorInfo || errorInfo.errorCode !== VIPRLibraries.LOAD_DEFINITION_ERROR) {
  244. _this.logger.error('An error occurred while re-rendering test', _arguments, _this.visAPI, error);
  245. }
  246. } else {
  247. _this.logger.warn(errorMessage, { datasetName: dataSourceName });
  248. }
  249. } else {
  250. // in case the render is aborted mid-render, log as debug but don't throw.
  251. // Treat as normal exception
  252. _this.logger.debug('An error occurred while re-rendering', _arguments, _this.visAPI);
  253. }
  254. });
  255. }
  256. });
  257. },
  258. /**
  259. * Called during setDefinition if the renderer changes (e.g. from RaveView to GridView)
  260. * In this case, we need to load a new view/control and remove the old view from rendering etc.
  261. *
  262. */
  263. onChangeRenderer: function onChangeRenderer(event) {
  264. var sender = event && event.sender;
  265. this._getRenderState().lastSizeRendered = null;
  266. this._getRenderState().reloadAllRenderSteps(); //Clear the loaded steps in the render sequence.
  267. if (sender !== 'UndoRedoController') {
  268. var undoRedoTransactionId = event && event.data && event.data.undoRedoTransactionId;
  269. var options = {
  270. undoRedoTransactionId: undoRedoTransactionId,
  271. transactionToken: event && event.data && event.data.transactionToken
  272. };
  273. this.ownerWidget.setPreferredSize(this.visAPI.getDefinition().preferredSize, options);
  274. }
  275. return this.render({ refreshAll: true, initial: false });
  276. },
  277. /**
  278. * Gets the last rendered size from the current render state.
  279. */
  280. getLastSizeRendered: function getLastSizeRendered() {
  281. return this._renderState ? this._renderState.lastSizeRendered : null;
  282. },
  283. /**
  284. * @returns true if the initial render is complete.
  285. */
  286. firstRenderComplete: function firstRenderComplete() {
  287. return this._getRenderState().firstRenderComplete;
  288. },
  289. /**
  290. * Perform all steps of the render sequence that apply to a given renderContext.
  291. * When done, if the renderState is complete, call the view to render the visualization.
  292. * If not, resolve anyway....the render will be done by completing the steps for a
  293. * subsequent render context.
  294. *
  295. * @param renderContext - information collected by a particular call to render or reRender (such as the view, control, data etc).
  296. */
  297. _render: function _render(options) {
  298. var contentStateError = this.content && this.content.getFeature('state').getError();
  299. var isMissingDataSource = contentStateError && contentStateError.getMessage() === 'dwErrorMissingDataset';
  300. if (isMissingDataSource || !options.refresh && !options.refreshAll) {
  301. var widgetError = this.ownerWidget && this.ownerWidget.getAPI().getVisApi().getInvalidReason();
  302. var _contentStateError = this.content && this.content.getFeature('state').getError();
  303. if (widgetError || _contentStateError) {
  304. // Cannot render this visualization
  305. return Promise.reject(new Error(widgetError && widgetError.msg || _contentStateError && _contentStateError.getMessage()));
  306. }
  307. }
  308. this.ownerWidget.renderStart(options);
  309. // The main steps in the renderSequence.
  310. // Depending on the view, some steps may not apply and simply resolve immediately.
  311. // A particular renderContext might just be responsible for refreshing data. In this case,
  312. // other steps will resolve immediately and pass the context through.
  313. if (this._isRenderSequenceAPIReady()) {
  314. return this._renderAPI.triggerRenderSequence(options);
  315. } else {
  316. return this.process(options);
  317. }
  318. },
  319. _initializeStep: function _initializeStep(step) {
  320. this._getRenderState().initCurrentContext(step);
  321. },
  322. /**
  323. * A "step" in the render sequence is complete IF
  324. * this renderContext is responsible for this step and has initialized it
  325. * OR if this renderContext is not responsible for this step
  326. * (eg: a renderContext when a filter changes is only responsible for fetching data but
  327. * the initial render is responsible for fetching everything...unless a data change happens fast).
  328. *
  329. * @param renderContext - information collected by a particular call to render or reRender (such as the view, control, data etc).
  330. * @param stepName The step (one of visView, visControl, data, visSpec, highlighter)
  331. */
  332. _isStepComplete: function _isStepComplete(renderContext, stepName) {
  333. var contextForThisStep = this._getRenderState().getCurrentContext(stepName);
  334. var contextMatch = renderContext === contextForThisStep;
  335. return !contextMatch || contextMatch && renderContext[stepName];
  336. },
  337. /**
  338. * This function is executed when a step is completed to initialize
  339. * the appropriate member of renderContext
  340. * @param renderContext - information collected by a particular call to render or reRender (such as the view, control, data etc).
  341. * @param step - one of the steps in the render process like 'visView', 'visControl' or 'data'
  342. * @param value - the value to assign the render context step member to.
  343. * @returns true if the current context step corresponds to this renderInfo, false otherwise.
  344. */
  345. _completeStep: function _completeStep(renderContext, step, value) {
  346. renderContext[step] = value;
  347. var currentStep = this._renderState ? this._renderState.getCurrentContext(step) : null;
  348. var stepMatch = currentStep && currentStep.id === renderContext.id;
  349. return !!currentStep && stepMatch;
  350. },
  351. /**
  352. * Helper for it test.
  353. */
  354. getVisControl: function getVisControl() {
  355. return this._getRenderState().getCurrentContextData('visControl');
  356. },
  357. getVisSpec: function getVisSpec() {
  358. return this._getRenderState().getCurrentContextData('visSpec');
  359. },
  360. /**
  361. * Create a render sequence of task definitions with the custom tasks
  362. * when the custom tasks are omitted, default sequence is returned.
  363. * Custom tasks will overwrite default tasks if they have the same id.
  364. * @param sequence - array of custom task definitions (optional)
  365. * @returns array of task definitions
  366. */
  367. createRenderSequence: function createRenderSequence() {
  368. var _this2 = this;
  369. var sequence = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  370. // Make sure to clone DEFAULT_SEQUENCE
  371. this._sequence = DEFAULT_SEQUENCE.concat([]);
  372. var newSequences = Array.isArray(sequence) ? sequence : [sequence];
  373. newSequences.forEach(function (newSequence) {
  374. var index = _this2._sequence.findIndex(function (defaultSequence) {
  375. return defaultSequence.id === newSequence.id;
  376. });
  377. if (index === -1) {
  378. _this2._sequence.push(newSequence);
  379. } else {
  380. _this2._sequence[index] = newSequence;
  381. }
  382. });
  383. this._createDependenciesFromBlockers();
  384. // Validate the dependencies for each step
  385. this._sequence.forEach(function (step) {
  386. _this2._validateDependencies(step.id);
  387. });
  388. return this._sequence;
  389. },
  390. _createDependenciesFromBlockers: function _createDependenciesFromBlockers() {
  391. var _this3 = this;
  392. this._sequence.forEach(function (step) {
  393. if (step.blocks) {
  394. step.blocks.forEach(function (id) {
  395. var blockedTask = _this3._getSequenceTask(id);
  396. if (blockedTask) {
  397. var dependencies = blockedTask.dependencies || [];
  398. if (dependencies.indexOf(step.id) === -1) {
  399. dependencies.push(step.id);
  400. }
  401. }
  402. });
  403. delete step.blocks;
  404. }
  405. });
  406. },
  407. _getSequenceTask: function _getSequenceTask(id) {
  408. return this._sequence.find(function (item) {
  409. return item.id === id;
  410. });
  411. },
  412. _validateDependencies: function _validateDependencies(taskId, currentDeps) {
  413. var _this4 = this;
  414. var dependencies = currentDeps || [];
  415. if (dependencies.indexOf(taskId) !== -1) {
  416. throw new Error('The task with id "' + taskId + '" has a a circular dependency: ' + currentDeps);
  417. }
  418. dependencies.push(taskId);
  419. var task = this._getSequenceTask(taskId);
  420. if (task.dependencies) {
  421. task.dependencies.forEach(function (depItem) {
  422. if (!_this4._getSequenceTask(depItem)) {
  423. throw new Error('The task with id "' + taskId + '" has a missing dependency "' + depItem + '"');
  424. }
  425. var clonedDepArray = dependencies.concat([]);
  426. _this4._validateDependencies(depItem, clonedDepArray);
  427. });
  428. }
  429. },
  430. /*
  431. * 'For each step in the render sequence, build a map entry that includes the following members:
  432. * "data" : {
  433. * id: {step.id},
  434. * resolve: {Execution reolve function},
  435. * reject: {Execution reject function}
  436. * whenStepIsComplete: { thisStepIsCompletePromise },
  437. * whenDepsAreComplete: [ dependencyStepIsCompletePromises ]
  438. * }
  439. *
  440. */
  441. _createExecutionState: function _createExecutionState(renderContext) {
  442. var _this5 = this;
  443. // Hold on to the previous execution state.
  444. // we might need to coordinate between the execution of steps between different runs
  445. // Render#1 (full render): dataTask1 --> renderTask1
  446. // Render#2 (partial resize render): renderTask2
  447. // In render#2, renderTask2 should also be blocked until dataTask1 of Render#1 is complete
  448. var previousExecutionStateMap = null;
  449. if (this._isExecuting) {
  450. previousExecutionStateMap = this.executionStateMap;
  451. }
  452. this.executionStateMap = {};
  453. // Attach the whenStepIsComplete promise
  454. this._sequence.forEach(function (step) {
  455. var execution = {
  456. renderId: renderContext.id,
  457. id: step.id
  458. };
  459. _this5.executionStateMap[step.id] = execution;
  460. execution.whenStepIsComplete = new Promise(function (resolve, reject) {
  461. execution.resolve = resolve;
  462. execution.reject = reject;
  463. });
  464. // eslint-disable-next-line
  465. execution.whenStepIsComplete.catch(function (error) {
  466. //defined to avoid bluebird unhandled rejection warning message
  467. });
  468. });
  469. // Attach the whenDepsAreComplete promise
  470. this._sequence.forEach(function (step) {
  471. var dependentStepCompletePromises = [];
  472. if (step.dependencies) {
  473. step.dependencies.forEach(function (dep) {
  474. dependentStepCompletePromises.push(_this5.executionStateMap[dep].whenStepIsComplete);
  475. _this5._trace.taskDependency(step, renderContext, dep);
  476. // if a blocking step is not part of the render sequence,
  477. // check if it is in the previous execution state map and wait for it.
  478. var previousExecutionStepState = previousExecutionStateMap && previousExecutionStateMap[dep];
  479. if (previousExecutionStepState && !_this5._isPartOfTheCurrentRun(renderContext, dep)) {
  480. dependentStepCompletePromises.push(previousExecutionStepState.whenStepIsComplete);
  481. _this5._trace.taskDependency(step, renderContext, {
  482. renderId: previousExecutionStepState.renderId,
  483. id: dep
  484. });
  485. }
  486. });
  487. }
  488. _this5.executionStateMap[step.id].whenDepsAreComplete = Promise.all(dependentStepCompletePromises);
  489. });
  490. },
  491. getTaskExecutionState: function getTaskExecutionState(id) {
  492. return this.executionStateMap[id];
  493. },
  494. /**
  495. * checks if the passed renderContext corresponds to the latest run
  496. * it is different from @see _isPartOfTheCurrentRun which checks if the
  497. * renderId associated to a step corresponds to the one associated to the current context
  498. * @param {number} renderContext - renderContext of a sequence run
  499. * @returns {boolean} true if it corresponds to the last run, false otherwise
  500. */
  501. isLastRun: function isLastRun(renderContext) {
  502. return renderContext.id === this._renderId;
  503. },
  504. /**
  505. *
  506. * checks if the renderId defined in the passed renderContext for the specified step corresponds to the one
  507. * stored in the current context
  508. * @param {Object} renderContext
  509. * @param {String} stepName - name of the steps being processed
  510. * @returns {boolean} true if it corresponds to the
  511. */
  512. _isPartOfTheCurrentRun: function _isPartOfTheCurrentRun(renderContext, stepName) {
  513. var currentStepContext = this._getRenderState().getCurrentContext(stepName);
  514. return currentStepContext && currentStepContext.id === renderContext.id;
  515. },
  516. /**
  517. * Recursively collect all the dependent steps (including dependents of dependents) among the sequence
  518. * @param {string} id - id of step
  519. * @param {object[]} sequence - sequence of all steps
  520. */
  521. _getAllDependencies: function _getAllDependencies(id, sequence) {
  522. var _this6 = this;
  523. var dependencies = {};
  524. _.each(sequence, function (step) {
  525. if (step.dependencies && step.dependencies.indexOf(id) > -1) {
  526. dependencies[step.id] = step;
  527. _.extend(dependencies, _this6._getAllDependencies(step.id, sequence));
  528. }
  529. });
  530. return dependencies;
  531. },
  532. /**
  533. * Apply the refresh flag to all dependent render sequence steps
  534. * @param {object[]} sequence - sequence of render sequence steps
  535. * @param {object} options - render options
  536. * @return render options including refresh on all dependents
  537. */
  538. _applyRefreshDependencies: function _applyRefreshDependencies(sequence, options) {
  539. var _this7 = this;
  540. if (options.refresh) {
  541. // walk through each steps to be refreshed
  542. _.each(options.refresh, function (refreshVal, id) {
  543. if (refreshVal) {
  544. _.each(_this7._getAllDependencies(id, sequence), function (step) {
  545. options.refresh[step.id] = true;
  546. });
  547. }
  548. });
  549. }
  550. return options;
  551. },
  552. /**
  553. * Create an identifier that can be used of the step list is modified
  554. * For now, the implementation is only using the step ids.
  555. */
  556. _getWidgetStepsIdentifier: function _getWidgetStepsIdentifier(steps) {
  557. return steps.map(function (step) {
  558. return step.id;
  559. }).toString();
  560. },
  561. /**
  562. * Make sure that the sequence defintion is created and that it reflects
  563. * the latest enabled widget features.
  564. *
  565. */
  566. _ensureSequenceUpToDate: function _ensureSequenceUpToDate() {
  567. // this list of extra render steps is for babckward compatibility
  568. // and to support the legacy widget features.
  569. // Once all features move to the official content feature api and use the RenderStepProviderAPI,
  570. // then this will no longer be needed.
  571. var widgetExtraSteps = this.ownerWidget.getExtraRenderSequenceSteps();
  572. // Get the extra steps registered by the features providers
  573. if (this._isRenderSequenceAPIReady()) {
  574. widgetExtraSteps = widgetExtraSteps.concat(this._renderAPI.getProvidersRenderStepList());
  575. }
  576. if (this.widgetExtraStepsIdentifier !== this._getWidgetStepsIdentifier(widgetExtraSteps)) {
  577. // The widget render steps have changed. We need to recreate the sequence
  578. this._sequence = null;
  579. }
  580. if (!this._sequence) {
  581. this.createRenderSequence(widgetExtraSteps);
  582. // Keep track of the widget steps in order to detect when they change
  583. this.widgetExtraStepsIdentifier = this._getWidgetStepsIdentifier(widgetExtraSteps);
  584. }
  585. },
  586. /**
  587. * Process the sequence of tasks
  588. * @param renderContext - the render context
  589. * @returns a promise
  590. */
  591. process: function process(options) {
  592. var _this8 = this;
  593. this._ensureSequenceUpToDate();
  594. return this._loadTasks(this._sequence).then(function (tasks) {
  595. return _this8._preProcessDataOptions(options).then(function (options) {
  596. options = _this8._applyRefreshDependencies(_this8._sequence, options);
  597. var renderContext = _this8._renderState.addRenderContext(options);
  598. _this8._logRenderContext('_render', renderContext);
  599. _this8._trace.startRender(tasks, renderContext, options);
  600. return _this8._processTasks(tasks, renderContext);
  601. });
  602. });
  603. },
  604. /**
  605. * Prior to adding the renderContext, check for the 'dataIfQueryChanged' option.
  606. * If specified, check if the query changed and include/exclude the data step.
  607. * @param options an options object (or null)
  608. * @returns a promise with the original options (or the updated options if applicable).
  609. */
  610. _preProcessDataOptions: function _preProcessDataOptions(options) {
  611. if (options && options.refresh && options.refresh.dataIfQueryChanged) {
  612. delete options.refresh.dataIfQueryChanged;
  613. return this._queryChanged().then(function (changed) {
  614. if (changed.isRenderNeeded) {
  615. options.refresh.data = true;
  616. } else {
  617. delete options.refresh.data;
  618. }
  619. return options;
  620. });
  621. }
  622. return Promise.resolve(options);
  623. },
  624. _queryChanged: function _queryChanged() {
  625. // TODO Wrap to async result when use new query API. Need to be cleaned once switch to query api.
  626. if (this.ownerWidget.useNewQueryApi()) {
  627. var internalQueryExecution = this.content.getFeature('DataQueryExecution.internal');
  628. return Promise.resolve({
  629. isRenderNeeded: internalQueryExecution.queryChanged()
  630. });
  631. } else {
  632. return this.visAPI.queryChanged();
  633. }
  634. },
  635. /*
  636. * Execute a list of tasks.
  637. * This function will create a new execution state, and will execute all
  638. * the independent tasks in parallel and only synchronize the dependent tasks.
  639. *
  640. */
  641. _processTasks: function _processTasks(tasks, renderContext) {
  642. var _this9 = this;
  643. this._isExecuting = true;
  644. // create the execution promises that will coordinate the steps dependency execution
  645. this._createExecutionState(renderContext);
  646. var taskProcessor = this._getTaskProcessor();
  647. // TODO temporarily add useAPI flag to renderContext
  648. // Final cleaning should search useAPI and clean all related code.
  649. renderContext.useAPI = this.ownerWidget.useNewQueryApi();
  650. var processTaskPromises = [];
  651. // let renderSequenceFeature = this.contentApi.getFeature('renderSequence.internal');
  652. tasks.forEach(function (task) {
  653. var taskExecution = _this9.getTaskExecutionState(task.id);
  654. processTaskPromises.push(taskExecution.whenDepsAreComplete.then(function () {
  655. _this9._trace.startTask(task, renderContext);
  656. try {
  657. return taskProcessor.startTaskExecution(task, renderContext).then(function () {
  658. _this9._trace.endTask(task, renderContext, 'SUCCEEDED');
  659. taskProcessor.resolveTaskExecution(taskExecution, task, renderContext);
  660. }).catch(function (err) {
  661. _this9._trace.endTask(task, renderContext, 'FAILED', err);
  662. taskProcessor.rejectTaskExecution(taskExecution, err, task, renderContext);
  663. throw err;
  664. });
  665. } catch (err) {
  666. _this9._trace.endTask(task, renderContext, 'FAILED', err);
  667. taskProcessor.rejectTaskExecution(taskExecution, err, task, renderContext);
  668. throw err;
  669. }
  670. }, function (err) {
  671. _this9._trace.endTask(task, renderContext, 'DEPENDENCY_FAILED', err);
  672. taskExecution.reject(err);
  673. throw err;
  674. }));
  675. });
  676. return Promise.all(processTaskPromises).then(function () {
  677. _this9._isExecuting = false;
  678. return renderContext;
  679. });
  680. },
  681. _resolvePath: function _resolvePath(path) {
  682. if (!path || path.length === 0) {
  683. return null;
  684. }
  685. var resolved = path;
  686. var relativeIndex = path.indexOf('./');
  687. if (relativeIndex > -1) {
  688. // @todo support for multi-level relative paths
  689. resolved = BASE_PATH + path.substr(relativeIndex + 2);
  690. }
  691. return resolved;
  692. },
  693. _getSequenceModulePaths: function _getSequenceModulePaths(sequence) {
  694. var _this10 = this;
  695. return _.chain(sequence)
  696. // extract the resolved path
  697. .map(function (_ref) {
  698. var modulePath = _ref.modulePath;
  699. return _this10._resolvePath(modulePath);
  700. }).value();
  701. },
  702. _loadTasks: function _loadTasks(sequence) {
  703. var _this11 = this;
  704. var paths = this._getSequenceModulePaths(sequence);
  705. return DynamicFileLoader.load(paths).then(function (Modules) {
  706. return _.map(sequence, function (step, index) {
  707. var Module = step.module || Modules[index];
  708. return {
  709. instance: new Module({
  710. id: step.id,
  711. owner: _this11,
  712. ownerWidget: _this11.ownerWidget,
  713. logger: _this11.logger,
  714. visAPI: _this11.visAPI,
  715. dashboardApi: _this11.dashboardApi,
  716. content: _this11.content
  717. }, step.moduleOptions),
  718. id: step.id
  719. };
  720. });
  721. });
  722. },
  723. _logRenderContext: function _logRenderContext(stage, renderContext) {
  724. var renderTimeInfo = 'render sequence stage:' + stage + ' {rendering ' + this.visAPI.id + ':';
  725. _.each(_.keys(renderContext), function (key) {
  726. if (key !== 'id') {
  727. var c = this._getRenderState().getCurrentContext(key);
  728. renderTimeInfo += key + '(' + (c ? c.id : '<NULL>') + ')';
  729. }
  730. }.bind(this));
  731. var model = this.ownerWidget.model ? this.ownerWidget.model.toJSON() : 'NULL';
  732. this.logger.debug(renderTimeInfo + ', id=' + renderContext.id + '}', model);
  733. },
  734. showError: function showError(msg, params, type) {
  735. var state = this.content.getFeature('state.internal');
  736. if (state) {
  737. var error = new APIError({ 'msg': msg, 'params': params }, { 'type': type });
  738. state.setError(error);
  739. }
  740. },
  741. enableTrace: function enableTrace() {
  742. var enable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  743. return this._trace.enable(enable);
  744. },
  745. clearTrace: function clearTrace() {
  746. return this._trace.clear();
  747. },
  748. getTrace: function getTrace() {
  749. return this._trace.getTrace();
  750. }
  751. });
  752. return VisRenderSequence;
  753. });
  754. //# sourceMappingURL=VisRenderSequence.js.map