StandaloneLiveWidget.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. 'use strict';
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. /*
  4. *+------------------------------------------------------------------------+
  5. *| Licensed Materials - Property of IBM
  6. *| IBM Cognos Products: Dashboard
  7. *| (C) Copyright IBM Corp. 2019, 2020
  8. *|
  9. *| US Government Users Restricted Rights - Use, duplication or disclosure
  10. *| restricted by GSA ADP Schedule Contract with IBM Corp.
  11. *+------------------------------------------------------------------------+
  12. */
  13. define(['./LiveWidget', '../../lib/@waca/core-client/js/core-client/ui/core/Events', '../../lib/@waca/core-client/js/core-client/utils/LoadCSSPromise', './nls/StringResources', 'underscore', 'dashboard-core/js/canvas/Content', 'dashboard-core/js/dashboard/model/LayoutModel', 'dashboard-core/js/features/content/InlineFeatures', './models/LiveWidgetModel'], function (LiveWidget, Events, LoadCSSPromise, StringResources, _, ContentImpl, LayoutModel, InlineFeatures, LiveWidgetModel) {
  14. var isCSSLoaded = false;
  15. var StandaloneLiveWidget = function () {
  16. /**
  17. * Create an instance of the live widget API
  18. *
  19. * @constructor
  20. * @param {Object} options.node Dom node where the live widget content will be created
  21. * @param {Object} options.glassContext Glass context that provide access to glass API
  22. * @param {Object} options.spec The live widget spec json.
  23. * @param {Object} options.inlineContentFeatures Content features to be loaded inline
  24. * @param {QueryResultsAPI} options.queryResults If provided, the LiveWidgetPreview will not issue a query for data and will use the queryResults instead
  25. * @example
  26. * let LiveWidget = new LiveWidgetAPI({
  27. * node: document.getElementById('containerDivId'),
  28. * glassContext: this.glassContext,
  29. * inlineContentFeatures: [{
  30. * "name": "QueryResultOverride",
  31. * "id": "com.ibm.bi.dashboard.live-features.queryResultOverride",
  32. * "class": "dashboard-analytics/features/widget/queryResultOverride/queryResultOverride"
  33. * }],
  34. * spec: {
  35. * "data": {
  36. * "dataViews": [
  37. * {
  38. * "model":{
  39. * "assetId": "iB3884FE9ACF64BD2B8F0879A8C34B978",
  40. * "type": "uploadedFile"
  41. * }
  42. * "dataItems": [
  43. * {
  44. * "itemId": "HollywoodMovies_xls.Year_",
  45. *
  46. * },
  47. * {
  48. * "itemId": "HollywoodMovies_xls.Rotten_Tomatoes"
  49. * }
  50. * ]
  51. * }
  52. * ]
  53. * }
  54. * }
  55. * });
  56. */
  57. function StandaloneLiveWidget(options) {
  58. _classCallCheck(this, StandaloneLiveWidget);
  59. this._destroyed = false;
  60. this.spec = JSON.parse(JSON.stringify(options.spec));
  61. this.spec.id = _.uniqueId('LW');
  62. this.spec.type = 'live';
  63. this.board = options.board;
  64. this.node = options.node;
  65. this._glassContext = options.glassContext;
  66. this.runtimeEnv = options.runtimeEnv;
  67. this.predictData = options.predictData;
  68. this.isPredictPreview = options.isPredictPreview;
  69. this._inlineContentFeatures = options.inlineContentFeatures || [];
  70. this._queryResults = options.queryResults;
  71. this._managesOwnQueries = options.managesOwnQueries;
  72. this._LayoutModelClass = options.layoutModelClass || LayoutModel;
  73. // TODO - temporary until explore starting sending the dashboard Api
  74. if (this.runtimeEnv && !this.runtimeEnv.dashboardApi) {
  75. this.runtimeEnv.dashboardApi = this._glassContext.appController.getCurrentContentView().getDashboardApi();
  76. }
  77. if (this.runtimeEnv && !this.runtimeEnv.dashboardFeatureLoader) {
  78. // Needed to be able to build a content API for the live widget preview
  79. this.runtimeEnv.dashboardFeatureLoader = this.runtimeEnv.dashboardApi.getFeature('DashboardFeatureLoader.internal');
  80. }
  81. this._defaultEventRouter = new Events();
  82. this.interactivitySettings = {
  83. isClickDisabled: !options.enableClick,
  84. isPanAndZoomDisabled: !options.enablePanAndZoom,
  85. isHoverDisabled: !options.enableHover,
  86. tabNavigation: options.tabNavigation
  87. };
  88. this.featureSet = options.featureSet;
  89. }
  90. StandaloneLiveWidget.prototype._createEnvironment = function _createEnvironment() {
  91. var _this = this;
  92. if (this.runtimeEnv) {
  93. return Promise.resolve();
  94. }
  95. return this._glassContext.getSvc('.DashboardRuntime').then(function (svc) {
  96. var options = {
  97. spec: svc.getDefaultSpec()
  98. };
  99. options.spec.widgets[_this.spec.id] = _this.spec;
  100. options.spec.name = _this.board && _this.board.name || options.spec.name;
  101. options.spec.version = _this.board && _this.board.version || options.spec.version;
  102. return svc.getRuntimeEnvironment(options).then(function (env) {
  103. // overwrite with the upgraded spec
  104. _this.spec = env.boardModel.widgets[_this.spec.id];
  105. var promises = [];
  106. _this.runtimeEnv = env;
  107. if (env.cssStyles && !isCSSLoaded) {
  108. isCSSLoaded = true;
  109. env.cssStyles.forEach(function (style) {
  110. promises.push(LoadCSSPromise.load(style, null, null, [{
  111. type: 'text/css'
  112. }]));
  113. });
  114. }
  115. return Promise.all(promises);
  116. });
  117. });
  118. };
  119. /**
  120. * Render the live widget instance. This method will execute the query, wait for it and render when the data is complete.
  121. * @function LiveWidgetAPI#render
  122. * @params {options} object that describes the live widget
  123. * @return {Promise} promise that will be resolved when the render is complete or reject in the case of an error
  124. * @example
  125. * liveWidget.render().then(()=>{
  126. * console.debug('render is complete');
  127. * }).catch(()=>{
  128. * console.debug('An error occured');
  129. * });
  130. */
  131. StandaloneLiveWidget.prototype.render = function render() {
  132. var _this2 = this;
  133. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  134. return this._createEnvironment().then(function () {
  135. return _this2._updateModelIds();
  136. }).then(function () {
  137. return _this2._createContent(options.isPreview);
  138. }).then(function (content) {
  139. if (_this2._queryResults) {
  140. _this2._inlineContentFeatures = _this2._addQueryOverrideFeature();
  141. }
  142. return Promise.all([content, content.registerFeatures(_this2._inlineContentFeatures)]);
  143. }).then(function (_ref) {
  144. var content = _ref[0];
  145. if (_this2._queryResults) {
  146. content.getFeature('QueryResultOverride').setQueryResults(_this2._queryResults);
  147. }
  148. options.widgetModel = content.widgetModel;
  149. options.initialConfigJSON = content.widgetModel;
  150. options = _.extend(options, {
  151. dashboardApi: _this2.runtimeEnv.dashboardApi,
  152. eventRouter: _this2.runtimeEnv.eventRouter || _this2._defaultEventRouter,
  153. registry: _this2.runtimeEnv.dashboardApi.getWidgetRegistry ? _this2.runtimeEnv.dashboardApi.getWidgetRegistry().live : {},
  154. contentFeatureLoader: _this2._createContentFeatureLoader(content),
  155. content: content
  156. });
  157. var widgetReady = void 0;
  158. if (!_this2.widget) {
  159. _this2.contentNode = _this2.contentNode || options.el;
  160. _this2.node.appendChild(_this2.contentNode);
  161. // Mock ability for unit tests
  162. var LiveWidgetClass = _this2._liveWidgetClass || LiveWidget;
  163. _this2.widget = new LiveWidgetClass(options);
  164. widgetReady = _this2.widget.initialize();
  165. } else {
  166. widgetReady = Promise.resolve();
  167. }
  168. return widgetReady;
  169. }).then(function () {
  170. if (!_this2._destroyed) {
  171. return _this2.widget.onContainerReady({
  172. model: _this2.widget.model
  173. });
  174. }
  175. }).then(function () {
  176. if (!_this2._destroyed) {
  177. return _this2.widget.render(null /* renderOptions */, false /* isInteractive */);
  178. }
  179. }).then(function () {
  180. if (!_this2._destroyed) {
  181. return _this2.widget.whenRenderComplete();
  182. }
  183. }).catch(function (error) {
  184. var errorId = error && error.message;
  185. if (errorId === 'promptingIsDisabled') {
  186. // don't need to localize this internal error
  187. throw error;
  188. }
  189. var datasetName = error && error.datasetName;
  190. //catches the exceptions thrown from VisRenderSequence and localize it so that
  191. //the caller of liveWidgetPreview eg. vis-recommnder can render the exceptions;
  192. if (errorId && datasetName) {
  193. var errorMessage = StringResources.get(errorId, { datasetName: datasetName });
  194. throw new Error(errorMessage);
  195. } else {
  196. throw error;
  197. }
  198. });
  199. };
  200. // TODO: look at moving the content creation earlier in the object construction/initialization to provide a mechanism
  201. // for content feature providers to work with the content (getFeature().setXYZ())
  202. // Then we can move this inline feature and the _queryResults to the provider.
  203. StandaloneLiveWidget.prototype._addQueryOverrideFeature = function _addQueryOverrideFeature() {
  204. this._inlineContentFeatures.push({
  205. containerId: 'com.ibm.bi.dashboard.content-features',
  206. name: 'QueryResultOverride',
  207. id: 'com.ibm.bi.dashboard.content-features.queryResultOverride',
  208. class: 'dashboard-analytics/features/widget/queryResultOverride/QueryResultOverride',
  209. types: ['widget.live']
  210. });
  211. return this._inlineContentFeatures;
  212. };
  213. StandaloneLiveWidget.prototype._createContentFeatureLoader = function _createContentFeatureLoader(content) {
  214. var _this3 = this;
  215. return {
  216. whenContentReady: function whenContentReady() /* contentId */{
  217. return Promise.resolve();
  218. },
  219. registerFeatureCollection: function registerFeatureCollection(contentId, collectionName) {
  220. return _this3.runtimeEnv.dashboardApi.findGlassCollection(collectionName).then(function (collectionItems) {
  221. return content.registerFeatures(collectionItems || []);
  222. });
  223. },
  224. registerFeature: function registerFeature(contentId, featureId, featureInstance, featureSpec) {
  225. return content.registerFeature(featureId, featureInstance, featureSpec);
  226. },
  227. registerDeprecatedFeature: function registerDeprecatedFeature(contentId, featureId, featureInstance) {
  228. return content.registerFeature(featureId, featureInstance, {}, true);
  229. }
  230. };
  231. };
  232. StandaloneLiveWidget.prototype._createContent = function _createContent(isPreview) {
  233. var _this4 = this;
  234. var dashboard = this.runtimeEnv.dashboardApi;
  235. return dashboard.findGlassCollection('com.ibm.bi.dashboard.content-features').then(function (collectionItems) {
  236. var contentFeatureCollection = InlineFeatures.concat(collectionItems || []);
  237. var boardModel = dashboard.getFeature('internal').getBoardModel();
  238. var logger = dashboard.getFeature('Logger');
  239. var layoutModel = new _this4._LayoutModelClass({
  240. type: 'widget',
  241. content: {
  242. features: {
  243. Visualization: {
  244. isPreview: isPreview === true
  245. }
  246. }
  247. }
  248. }, boardModel, logger);
  249. _this4.content = new ContentImpl({
  250. contentFeatureCollection: contentFeatureCollection,
  251. dashboardFeatures: _this4.runtimeEnv.dashboardFeatureLoader,
  252. boardModel: boardModel,
  253. layoutModel: layoutModel,
  254. canvas: dashboard.getCanvas(),
  255. widgetModel: new LiveWidgetModel(_this4.spec)
  256. });
  257. return _this4.content.initialize().then(function () {
  258. return _this4.content;
  259. });
  260. });
  261. };
  262. /**
  263. * Get preview title
  264. *
  265. * @return {string} livewidget preview title
  266. */
  267. StandaloneLiveWidget.prototype.getTitle = function getTitle() {
  268. if (this._destroyed) {
  269. return;
  270. }
  271. return this.widget.getTitle();
  272. };
  273. /**
  274. * Resize the preview. If the container size has changed since the last render,
  275. * a re-render will occur without re-executing the data query.
  276. */
  277. StandaloneLiveWidget.prototype.resize = function resize() {
  278. if (this._destroyed) {
  279. return;
  280. }
  281. if (this.widget) {
  282. return this.widget.resize();
  283. }
  284. };
  285. /**
  286. * Destroy the live widget instance.
  287. * This should be called when the live widget is no longer needed to free up resources.
  288. * @function LiveWidgetAPI#destroy
  289. */
  290. StandaloneLiveWidget.prototype.destroy = function destroy() {
  291. if (this._destroyed) {
  292. return;
  293. }
  294. this._destroyed = true;
  295. if (this.content) {
  296. this.content.destroy();
  297. this.content = null;
  298. }
  299. if (this.widget) {
  300. this.widget.destroy();
  301. this.widget = null;
  302. }
  303. if (this.contentNode) {
  304. this.contentNode.parentNode && this.contentNode.parentNode.removeChild(this.contentNode);
  305. this.contentNode = null;
  306. }
  307. };
  308. /**
  309. * Return the live widget spec. This will be the full spec and not the spec that was provide as input.
  310. *
  311. * @function LiveWidgetAPI#toJSON
  312. */
  313. StandaloneLiveWidget.prototype.toJSON = function toJSON() {
  314. if (!this._destroyed) {
  315. return this.widget.model.toJSON();
  316. }
  317. };
  318. /*
  319. * Private methods
  320. */
  321. StandaloneLiveWidget.prototype._updateModelIds = function _updateModelIds() {
  322. var _this5 = this;
  323. var dashboardApi = this.runtimeEnv.dashboardApi;
  324. var dataSources = dashboardApi.getFeature('DataSources');
  325. var dataSourceList = dataSources.getDataSourceList() || [];
  326. var promises = [];
  327. if (this.spec.data && this.spec.data.dataViews) {
  328. this.spec.data.dataViews.forEach(function (dataView) {
  329. if (!dataView.modelRef && dataView.model) {
  330. var sourceIdList = _.chain(dataSourceList).filter(function (ds) {
  331. return ds.getAssetId() === dataView.model.assetId;
  332. }).map(function (ds) {
  333. return ds.getId();
  334. }).value();
  335. if (sourceIdList.length > 0) {
  336. dataView.modelRef = sourceIdList[0];
  337. delete dataView.model;
  338. } else {
  339. if (!dataView.model.type) {
  340. promises.push(_this5._getAssetType(dataView.model.assetId).then(function (type) {
  341. dataView.model.type = type;
  342. _this5._convertModelToModelRef(dataView, dataSources);
  343. }));
  344. } else {
  345. _this5._convertModelToModelRef(dataView, dataSources);
  346. }
  347. }
  348. } else {
  349. if (dataView.modelRef && !dataView.model) {
  350. // this block is hit when the stand alone live widget is not added in the spec (case of footer view of driver analysis) and this method make sure the modelRef if specified is the active data source (scenario remove data item and drop column from different source in the same slot)
  351. var activeDataSourceId = dashboardApi.getActiveDataSourceId();
  352. if (activeDataSourceId && activeDataSourceId !== dataView.modelRef) {
  353. dataView.modelRef = activeDataSourceId;
  354. }
  355. }
  356. }
  357. });
  358. }
  359. return Promise.all(promises);
  360. };
  361. StandaloneLiveWidget.prototype._convertModelToModelRef = function _convertModelToModelRef(dataView, dataSources) {
  362. dataView.modelRef = dataSources.addDataSource(dataView.model);
  363. delete dataView.model;
  364. };
  365. StandaloneLiveWidget.prototype._getAssetType = function _getAssetType(assetId) {
  366. return this.runtimeEnv.dashboardApi.getGlassCoreSvc('.Ajax').ajax({
  367. url: 'v1/objects/' + assetId + '?fields=userInterfaces,defaultName,searchPath',
  368. type: 'GET',
  369. headers: {
  370. 'Accept': 'application/json'
  371. }
  372. }).then(function (response) {
  373. if (response.data && response.data.data && response.data.data.length > 0) {
  374. return response.data.data[0].type;
  375. }
  376. });
  377. };
  378. return StandaloneLiveWidget;
  379. }();
  380. return StandaloneLiveWidget;
  381. });
  382. //# sourceMappingURL=StandaloneLiveWidget.js.map