DataSourceList.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: Dashboard
  6. *| (C) Copyright IBM Corp. 2014, 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. define(['../../lib/@waca/dashboard-common/dist/ui/SearchableListView', 'text!./templates/DataSourceItem.html', '../nls/StringResources', '../../util/DashboardFormatter', '../glassControllers/MenuActionHelper', '../../DynamicFileLoader', '../../lib/@waca/dashboard-common/dist/lib/@ba-ui-toolkit/ba-graphics/dist/illustrations-js/add-data-source_128', 'jquery', 'underscore', 'react-dom', 'react', 'ca-ui-toolkit', '../glassControllers/AddSourceActionHandler'], function (SearchableListView, ItemTemplate, stringResources, Formatter, MenuActionHelper, DynamicFileLoader, em_add_sources, $, _, ReactDOM, React, Toolkit, AddSourceActionHandler) {
  13. 'use strict';
  14. var DATASOURCE_SELECTED_EVT = 'dataSourcePanel:dataSourceSelected';
  15. var Button = Toolkit.Button;
  16. var View = null;
  17. View = SearchableListView.extend({
  18. _baseEvents: {
  19. 'click .treeSortContainer': 'onSortClick',
  20. 'tap .treeSortContainer': 'onSortClick',
  21. 'click input[id^="relink_"]': 'onRelinkButtonClick'
  22. },
  23. events: {},
  24. itemTemplate: ItemTemplate,
  25. relinkDialogModule: 'dashboard-analytics/dataSources/dialogs/RelinkDialog',
  26. isVisible: true,
  27. /**
  28. * Model change:id event handler
  29. * operation before we rerender the data sources list
  30. * */
  31. onModelChangeHandler: function onModelChangeHandler() {
  32. //setTimeout to make sure all attributes are updated as an atomic operation
  33. window.setTimeout(this.render.bind(this), 10);
  34. },
  35. init: function init(attributes) {
  36. this.listItemClass = 'sourceLabelContainer';
  37. View.inherited('init', this, arguments);
  38. var panelAttributes = attributes || {};
  39. this.dashboardApi = panelAttributes.dashboardApi;
  40. this.panelController = attributes.panelController;
  41. this.dataSourcesSvc = this.dashboardApi.getFeature('dataSources.deprecated');
  42. this.isAlreadyRendered = this.panelController.getTransientState('dataSourceListRendered');
  43. this.panelController.setTransientState('dataSourceListRendered', true);
  44. this.registeredEvents = [];
  45. this.reactDomNode = null;
  46. },
  47. /**
  48. * Extend the BaseClass's _setupEvents function and add clicktap event handler for "do more" icon
  49. * @override
  50. * */
  51. _setupEvents: function _setupEvents() {
  52. View.inherited('_setupEvents', this, arguments);
  53. this.events['primaryaction ' + '.listitem .ellipsesButton'] = 'onDoMore';
  54. this.events['keydown ' + '.listitem .ellipsesButton'] = 'onKeyDown';
  55. this.events['contextmenu ' + '.listitem'] = 'onDoMore';
  56. },
  57. fetchDataSources: function fetchDataSources() {
  58. var dataSourceCollection = this.dataSourcesSvc.getSourcesCollection();
  59. if (dataSourceCollection) {
  60. this.dataSourceCollection = dataSourceCollection;
  61. this.registeredEvents.push(this.dataSourceCollection.on('change:assetId', function (payload) {
  62. this.aDataSources = dataSourceCollection.getSources();
  63. this.onModelChangeHandler(payload);
  64. }.bind(this)));
  65. this.registeredEvents.push(this.dataSourceCollection.on('change:state', function () {
  66. this.aDataSources = dataSourceCollection.getSources();
  67. this.render();
  68. }.bind(this)));
  69. this.registeredEvents.push(this.dataSourceCollection.on('add', function () {
  70. this.aDataSources = dataSourceCollection.getSources();
  71. this.render();
  72. }.bind(this)));
  73. this.registeredEvents.push(this.dataSourceCollection.on('remove', function () {
  74. this.aDataSources = dataSourceCollection.getSources();
  75. this.render();
  76. }.bind(this)));
  77. this.registeredEvents.push(this.dataSourceCollection.on('change:version', function (event) {
  78. var dataSourceId = event.model.id;
  79. var dataSource = this.dataSourceCollection.getSource(dataSourceId);
  80. this.dashboardApi.triggerDashboardEvent(DATASOURCE_SELECTED_EVT, {
  81. sender: dataSourceId,
  82. refreshDatasetAction: true
  83. });
  84. this.isAlreadyRendered = false;
  85. this.loadMetadata(dataSource, true);
  86. }.bind(this)));
  87. this.aDataSources = dataSourceCollection.getSources();
  88. } else {
  89. this._showError();
  90. }
  91. },
  92. _showError: function _showError() {
  93. DynamicFileLoader.load(['lib/@waca/dashboard-common/dist/ui/dialogs/MessageBox']).then(function (Modules) {
  94. var ErrorMsg = Modules[0];
  95. var title = stringResources.get('errorMessageTitle');
  96. var displayMessage = stringResources.get('errorLoadingDataSources');
  97. var msg = new ErrorMsg('error', title, displayMessage);
  98. msg.open();
  99. });
  100. },
  101. onRelinkButtonClick: function onRelinkButtonClick() {
  102. return false;
  103. },
  104. loadMetadata: function loadMetadata(dataSource, forceRefreshMetadata) {
  105. return this.panelController.loadMetadata(dataSource, forceRefreshMetadata, true);
  106. },
  107. /**
  108. * Extend Baseclass's onSelectItem event handler
  109. * @override
  110. **/
  111. onSelectItem: function onSelectItem(event) {
  112. if ($(event.target).parents('.caption').hasClass('missingRef') || !($(event.target).hasClass('datasetLabel') || $(event.target).hasClass('sourceLabelContainer')) || $(event.target).parents('.caption').hasClass('warning')) {
  113. return false;
  114. }
  115. var target = this.getTarget(event.target, 'listitem');
  116. var dsId = target.getAttribute('data-id');
  117. var dataSource = this.dataSourceCollection.getSource(dsId);
  118. this.dashboardApi.getFeature('DataSources').setActiveDataSourceId(dsId);
  119. this.dashboardApi.triggerDashboardEvent(DATASOURCE_SELECTED_EVT, {
  120. sender: dsId
  121. });
  122. this.$el.off();
  123. this.loadMetadata(dataSource);
  124. },
  125. prepareListItem: function prepareListItem(item) {
  126. item.cssClass = 'data-source';
  127. var sDate = null;
  128. var timezone = this.dashboardApi.getGlassCoreSvc('.UserProfile').preferences.timeZoneID || 'America/New_York';
  129. if (item.lastModified) {
  130. sDate = Formatter.format(item.lastModified, {
  131. type: 'date',
  132. formatLength: 'short',
  133. timezone: timezone
  134. });
  135. var sTime = Formatter.format(item.lastModified, {
  136. type: 'time',
  137. formatLength: 'short',
  138. timezone: timezone
  139. });
  140. if (sTime) {
  141. sDate = sDate + ' ' + sTime;
  142. }
  143. }
  144. item.title = stringResources.get('moreTitle');
  145. item.lastUpdatedString = sDate;
  146. return item;
  147. },
  148. prepareDataSourceList: function prepareDataSourceList(dataSources) {
  149. var _this = this;
  150. var aPrepDataSources = [];
  151. var whenNamesReady = [];
  152. var getName = function getName(source, item) {
  153. return source.getLocalizedName().then(function (name) {
  154. item.name = name;
  155. return source.exists();
  156. }).then(function (exists) {
  157. item.state = source.getState(); //State can be updated by the getLocalizedName() and exists() calls, so set it after those calls have finished.
  158. item.exists = exists;
  159. return source.getModificationTime();
  160. }).then(function (modificationTime) {
  161. item.lastModified = modificationTime;
  162. }).catch(function () {
  163. item.state = 'error';
  164. item.errorCode = '404';
  165. item.exists = false;
  166. // recover from error
  167. });
  168. };
  169. for (var i = 0; i < dataSources.length; i++) {
  170. var item = {
  171. type: dataSources[i].getType(),
  172. id: dataSources[i].getId()
  173. };
  174. if (dataSources[i].getIsOlapPackage()) {
  175. item.isOlapPackage = true;
  176. } else {
  177. item.isOlapPackage = false;
  178. }
  179. if (dataSources[i].lastModified) {
  180. item.lastModified = dataSources[i].lastModified;
  181. } else if (dataSources[i].lastUpdated) {
  182. item.lastUpdated = dataSources[i].lastUpdated;
  183. }
  184. aPrepDataSources.push(item);
  185. whenNamesReady.push(getName(dataSources[i], item));
  186. }
  187. var result = void 0;
  188. if (whenNamesReady.length > 0) {
  189. var always = function always() {
  190. _this.aPrepDataSources = aPrepDataSources;
  191. return aPrepDataSources;
  192. };
  193. result = Promise.all(whenNamesReady).then(always, always);
  194. } else {
  195. this.aPrepDataSources = aPrepDataSources;
  196. result = Promise.resolve(aPrepDataSources);
  197. }
  198. return result;
  199. },
  200. getListItems: function getListItems() {
  201. var result = void 0;
  202. if (this.aDataSources) {
  203. result = this.prepareDataSourceList(this.aDataSources);
  204. } else {
  205. this.fetchDataSources();
  206. if (this.aDataSources) {
  207. result = this.prepareDataSourceList(this.aDataSources);
  208. } else {
  209. result = Promise.reject(new Error());
  210. }
  211. }
  212. return result;
  213. },
  214. /**
  215. * Sets the visibility to true and calls render
  216. * @param options { preventRender: true } If preventRender is true the view will not be rendered
  217. */
  218. show: function show(options) {
  219. this.isVisible = true;
  220. if (options && options.preventRender === true) {
  221. return;
  222. }
  223. this.render();
  224. },
  225. render: function render() {
  226. var _this2 = this;
  227. this.dashboardApi.getFeature('DataSources').setActiveDataSourceId(null);
  228. this.dashboardApi.triggerDashboardEvent(DATASOURCE_SELECTED_EVT, {
  229. sender: null
  230. });
  231. return View.inherited('render', this, arguments).then(function () {
  232. if (_this2.isVisible) {
  233. _this2.panelController.clearIcons();
  234. return _this2.panelController.renderDataSourcePaneButtons('com.ibm.bi.dashboard.dataSourcePanel.list.buttons').then(function () {
  235. var dataSourceButtons = _this2.panelController.$el.find('.icons .ds_btn');
  236. if (dataSourceButtons && dataSourceButtons[0]) {
  237. dataSourceButtons[0].focus();
  238. }
  239. });
  240. }
  241. }).then(function () {
  242. $('.datasetContent').find('.loading').remove();
  243. var dataSourceListBox = _this2.$el.find('.list');
  244. dataSourceListBox.attr('title', stringResources.get('sourcePaneContentLabel'));
  245. // Add the empty indicator
  246. var dataSourcePanel = _this2.$el.find('.datasetPanel');
  247. dataSourcePanel.attr('data-pageindex', 0);
  248. var dataSourceItem = _this2.$el.find('.listitem');
  249. dataSourceItem.removeAttr('tabindex');
  250. dataSourceItem.removeAttr('aria-activedescendant');
  251. dataSourceItem.removeAttr('aria-selected');
  252. dataSourceItem.removeAttr('role');
  253. if (_this2.aDataSources.length === 0) {
  254. _this2._getEmptyDataSourceTemplate(dataSourceListBox);
  255. dataSourceListBox.addClass('emptyTableContent');
  256. dataSourceListBox.removeClass('list');
  257. dataSourceListBox.removeAttr('role');
  258. dataSourceListBox.removeAttr('aria-multiselectable');
  259. } else {
  260. _this2.$el.find('.listitem .sourceLabelContainer').first().attr('tabindex', 0);
  261. }
  262. // if we are rendering for the first time and we have one source,
  263. // we automatically open the source metadata
  264. if (!_this2.isAlreadyRendered) {
  265. _this2.isAlreadyRendered = true;
  266. if (_this2.aDataSources.length === 1 && (!_this2.aDataSources[0].getState() || _this2.aDataSources[0].getState() === 'ready')) {
  267. return _this2.loadMetadata(_this2.aDataSources[0]);
  268. } else if (_this2.aDataSources.length > 1) {
  269. var activeSourceId = _this2.dashboardApi.getActiveDataSourceId();
  270. if (activeSourceId) {
  271. var activeDataSource = _this2.aDataSources.find(function (dataSource) {
  272. return dataSource.getId() === activeSourceId;
  273. });
  274. if (activeDataSource) {
  275. return _this2.loadMetadata(activeDataSource);
  276. }
  277. }
  278. }
  279. }
  280. });
  281. },
  282. onAddDataSourceClicked: function onAddDataSourceClicked() {
  283. var addSourceHandler = new AddSourceActionHandler({ 'dashboardApi': this.dashboardApi });
  284. return addSourceHandler.execute();
  285. },
  286. _getEmptyDataSourceTemplate: function _getEmptyDataSourceTemplate(el) {
  287. var _this3 = this;
  288. var perspectiveLabel = this.dashboardApi.getApplicationLabel().toLowerCase();
  289. var selectSourcesText = stringResources.get('selectSourcesText', { app: perspectiveLabel });
  290. var selectSourcesLabel = stringResources.get('selectSourcesLabel');
  291. this.reactDomNode = el[0];
  292. ReactDOM.render(React.createElement(
  293. 'div',
  294. { className: 'emptyTableContentWrapper' },
  295. React.createElement(
  296. 'div',
  297. { className: 'emptyTableImage ba-theme-waca' },
  298. React.createElement(
  299. 'svg',
  300. { viewBox: em_add_sources.default.viewBox, focusable: 'false', height: '128px', 'margin-top': '3px' },
  301. React.createElement('use', { className: 'ba-graphics-themable', xlinkHref: '#' + em_add_sources.default.id, fill: '#8ee9d4' })
  302. )
  303. ),
  304. React.createElement(
  305. 'div',
  306. { className: 'emptyTableText line1', role: 'option', title: selectSourcesLabel, 'aria-label': selectSourcesLabel },
  307. selectSourcesLabel
  308. ),
  309. React.createElement(
  310. 'div',
  311. { className: 'emptyTableText line2', role: 'option', title: selectSourcesText, 'aria-label': selectSourcesText },
  312. selectSourcesText
  313. ),
  314. React.createElement(
  315. 'div',
  316. { className: 'selectSourceButton' },
  317. React.createElement(Button, {
  318. label: stringResources.get('selectSourcesLabel'),
  319. variant: 'solid',
  320. intent: 'primary',
  321. onClick: function onClick() {
  322. return _this3.onAddDataSourceClicked();
  323. },
  324. 'aria-label': selectSourcesLabel
  325. })
  326. )
  327. ), this.reactDomNode);
  328. return;
  329. },
  330. getCustomRenderProperties: function getCustomRenderProperties() {
  331. return {
  332. newStyle: true,
  333. sortFlyout: false,
  334. defaultSortIcon: 'wfg_modified_desc'
  335. };
  336. },
  337. /**
  338. * Context Menu Event handler, User clicks the doMore icon to launch data set context menu
  339. * */
  340. onDoMore: function onDoMore(evt) {
  341. var id = $(evt.target).parents('div.listitem').attr('data-id');
  342. var name = $(evt.target).parents('div.listitem').attr('data-name');
  343. var dataSource = this.dataSourceCollection.getSource(id);
  344. MenuActionHelper.getActionPayload({
  345. 'dashboardApi': this.dashboardApi,
  346. 'id': dataSource.getAssetId(),
  347. 'type': dataSource.getType(),
  348. 'event': evt,
  349. 'menuId': 'com.ibm.bi.dashboard.dataSources.dataSourceMenu',
  350. 'position': {
  351. pageX: evt.pageX,
  352. pageY: evt.pageY
  353. }
  354. }).then(function (payload) {
  355. payload.activeObject.dialogModule = this.relinkDialogModule;
  356. payload.activeObject.dataSource = dataSource;
  357. payload.activeObject.dataSourceName = name;
  358. payload.activeObject.dataSourceCollection = this.dataSourceCollection;
  359. this.dashboardApi.showContextMenu(payload);
  360. }.bind(this));
  361. return false;
  362. },
  363. _getSearchableItems: function _getSearchableItems() {
  364. return this.aPrepDataSources;
  365. },
  366. _getSearchableFieldValue: function _getSearchableFieldValue(value) {
  367. return value.name;
  368. },
  369. onSortClick: function onSortClick() {},
  370. _showNodeOnFocus: function _showNodeOnFocus($node) {
  371. $node.css('display', 'inline');
  372. $node.addClass('hideOnFocusChange');
  373. },
  374. _hideNodeOnUnfocus: function _hideNodeOnUnfocus($node) {
  375. $node.css('display', 'none');
  376. $node.removeClass('hideOnFocusChange');
  377. },
  378. _getNextFocusableSiblingElement: function _getNextFocusableSiblingElement($target) {
  379. var $nextSiblings = $target.nextAll('[tabindex="-1"]');
  380. var focusedItems = $target.nextAll('[tabindex="0"]');
  381. var currentfocusedItem = focusedItems.length && focusedItems.first()[0];
  382. var $newNodeToFocus = $($nextSiblings[0]);
  383. if (currentfocusedItem === $newNodeToFocus[0] || !$newNodeToFocus.length) {
  384. // item already focused.
  385. return;
  386. }
  387. if ($newNodeToFocus.css('display') === 'none') {
  388. this._showNodeOnFocus($newNodeToFocus);
  389. }
  390. return $newNodeToFocus;
  391. },
  392. _getPreviousFocusableSiblingElement: function _getPreviousFocusableSiblingElement($target) {
  393. var $previousSiblings = $target.prevAll('[tabindex="-1"]');
  394. var focusedItems = $target.prevAll('[tabindex="0"]');
  395. var currentfocusedItem = focusedItems.length && focusedItems.first()[0];
  396. var $newNodeToFocus = $($previousSiblings[0]);
  397. if (currentfocusedItem === $newNodeToFocus[0] || !$newNodeToFocus.length) {
  398. // item already focused.
  399. return;
  400. }
  401. if ($newNodeToFocus.css('display') === 'none') {
  402. this._showNodeOnFocus($newNodeToFocus);
  403. }
  404. if ($target.hasClass('focusable') && $target.hasClass('hideOnFocusChange')) {
  405. this._hideNodeOnUnfocus($target);
  406. }
  407. return $newNodeToFocus.length === 0 ? null : $newNodeToFocus;
  408. },
  409. rightArrowHandler: function rightArrowHandler($target) {
  410. return this._getNextFocusableSiblingElement($target);
  411. },
  412. leftArrowHandler: function leftArrowHandler($target) {
  413. return this._getPreviousFocusableSiblingElement($target);
  414. },
  415. _findRelatedListItem: function _findRelatedListItem($target) {
  416. var $t = $target;
  417. if ($target.hasClass('focusable')) {
  418. var $ancestor = $target.closest('.listitem');
  419. $t = $ancestor.find(this.itemClassSelector);
  420. }
  421. return $t;
  422. },
  423. downArrowHandler: function downArrowHandler($target) {
  424. var $relatedListItem = this._findRelatedListItem($target);
  425. if ($target.hasClass('focusable') && $target.hasClass('hideOnFocusChange')) {
  426. this._hideNodeOnUnfocus($target);
  427. }
  428. var $items = this.$(this.itemClassSelector + ':visible');
  429. return $($items[$items.index($relatedListItem) + 1]);
  430. },
  431. upArrowHandler: function upArrowHandler($target) {
  432. var $relatedListItem = this._findRelatedListItem($target);
  433. if ($target.hasClass('focusable') && $target.hasClass('hideOnFocusChange')) {
  434. this._hideNodeOnUnfocus($target);
  435. }
  436. var $items = this.$(this.itemClassSelector + ':visible');
  437. return $($items[$items.index($relatedListItem) - 1]);
  438. },
  439. _getCurrentlyFocusedItems: function _getCurrentlyFocusedItems() {
  440. return this.$('[tabindex="0"]');
  441. },
  442. _triggerFocusEvent: function _triggerFocusEvent($target) {
  443. var $relatedListItem = $target.closest('.listitem');
  444. this.trigger('focus:item', {
  445. id: $relatedListItem.attr('data-id')
  446. });
  447. },
  448. detach: function detach() {
  449. this.$el.detach();
  450. this.isVisible = false;
  451. },
  452. hide: function hide() {
  453. this.$el.hide();
  454. this.isVisible = false;
  455. },
  456. remove: function remove() {
  457. if (this.registeredEvents.length) {
  458. _.each(this.registeredEvents, function (collectionEvent) {
  459. if (collectionEvent) {
  460. collectionEvent.remove();
  461. }
  462. });
  463. this.registeredEvents = [];
  464. }
  465. this.aDataSources = null;
  466. this.dashboardApi = null;
  467. this.panelController = null;
  468. this.dataSourcesSvc = null;
  469. if (this.reactDomNode) {
  470. ReactDOM.unmountComponentAtNode(this.reactDomNode);
  471. }
  472. View.inherited('remove', this, arguments);
  473. }
  474. });
  475. return View;
  476. });
  477. //# sourceMappingURL=DataSourceList.js.map