SecurityObjectExplorerCopyMoveView.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. "use strict";
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: Cognos Analytics
  5. * Copyright IBM Corp. 2017, 2018
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['q', 'underscore', 'doT', 'bi/admin/nls/StringResource', 'bi/commons/ui/AccessibleView', 'bi/admin/account/services/SecurityObjectSelectorAdaptor', 'bi/admin/account/ui/SecurityObjectExplorerView', 'bacontentnav/ui/dialogs/NameCollisionDialog'], function (Q, _, dot, StringResource, SecurityObjectSelectorPane, SecurityObjectSelectorAdaptor, SecurityObjectExplorerView, NameCollisionDialog) {
  9. 'use strict'; //NOSONAR: meant to be strict
  10. var SecurityObjectExplorerCopyMoveView = SecurityObjectSelectorPane.extend({
  11. showBreadcrumbs: true,
  12. defaultTypes: ['namespaceFolder', 'group', 'role', 'account'],
  13. _breadCrumbs: null,
  14. _namespaceWritePermission: true,
  15. _accountExplorer: null,
  16. listAdaptor: null,
  17. title: '',
  18. multiSelect: false,
  19. targetLocation: {},
  20. init: function init(options) {
  21. SecurityObjectExplorerCopyMoveView.inherited('init', this, arguments);
  22. var cognosNamespaceOnly = options.cognosNamespaceOnly ? options.cognosNamespaceOnly : false;
  23. var listAdaptor = this._getNewSecurityObjectSelectorAdaptor({
  24. glassContext: this.glassContext,
  25. allowedSelectionTypes: options.allowedSelectionTypes,
  26. cognosNamespaceOnly: cognosNamespaceOnly,
  27. hideFilter: true
  28. });
  29. this.explorerView = this._getNewSecurityObjectExplorerView({
  30. glassContext: this.glassContext,
  31. showBreadcrumbs: true,
  32. hideSearch: options.hideSearch,
  33. includeToolbar: true,
  34. type: options.type,
  35. listAdaptor: listAdaptor,
  36. parent: this,
  37. hidePaging: true
  38. });
  39. },
  40. _getNewSecurityObjectExplorerView: function _getNewSecurityObjectExplorerView(options) {
  41. return new SecurityObjectExplorerView(options);
  42. },
  43. _getNewSecurityObjectSelectorAdaptor: function _getNewSecurityObjectSelectorAdaptor(options) {
  44. return new SecurityObjectSelectorAdaptor(options);
  45. },
  46. render: function render() {
  47. var deferred = Q.defer();
  48. this.explorerView.$el = this.$el;
  49. this.explorerView.render(this.$el).done(function () {
  50. this._renderSelectionTitle();
  51. this.bindEvents();
  52. this.targetLocation = this.explorerView._accountExplorer.getCurrentPath(); //reload the listView in order to readjust its flex box so it does not get overlapped by the footer
  53. this.explorerView.reloadList().done(function () {
  54. deferred.resolve();
  55. });
  56. }.bind(this));
  57. return deferred.promise;
  58. },
  59. _renderSelectionTitle: function _renderSelectionTitle() {
  60. var $explorerHeader = $('<span class="dialogContentHeader" display="block"></span>');
  61. var sep = $('<div>', {
  62. 'class': 'separator'
  63. });
  64. $explorerHeader.append(sep);
  65. var $title = $('<div>', {
  66. 'class': 'selectDestination'
  67. }).text(StringResource.get('selectDestination'));
  68. $explorerHeader.append($title);
  69. $('.dialogContent').prepend($explorerHeader); //Need to force the heights for the copy move dialog to render properly
  70. $('.dialogContent').addClass('admin_copyMoveDialogContent');
  71. $('.dialogContentHeader').next().css({
  72. "display": "flex",
  73. "flex-direction": "column",
  74. "height": "100%"
  75. });
  76. $('.dataTables_wrapper').css({
  77. "height": "100%"
  78. });
  79. $('.dataTables_scroll').css({
  80. "height": "100%"
  81. });
  82. },
  83. _renderDestinationLebel: function _renderDestinationLebel(destName) {
  84. var destination = StringResource.get('destination') + ' ' + destName;
  85. $('.copy-move-destinantion-label').text(destination);
  86. },
  87. getPathStringFromObjectSearchPath: function getPathStringFromObjectSearchPath(object) {
  88. var folderArray;
  89. if (object.type === 'namespaceFolder') {
  90. folderArray = object.searchPath.split('/');
  91. return this._convertCognosSearchPathSyntaxToTreePathSyntax(folderArray, folderArray.length - 1);
  92. } else {
  93. folderArray = object.searchPath.split(':');
  94. var pathString = "";
  95. for (var i = 1; i < folderArray.length - 1; i++) {
  96. pathString = pathString + "/" + folderArray[i];
  97. }
  98. return pathString;
  99. }
  100. },
  101. getPathStringFromTargetLoc: function getPathStringFromTargetLoc() {
  102. var folderArray = this.targetLocation.searchPath.split('/');
  103. return this._convertCognosSearchPathSyntaxToTreePathSyntax(folderArray, folderArray.length);
  104. },
  105. _convertCognosSearchPathSyntaxToTreePathSyntax: function _convertCognosSearchPathSyntaxToTreePathSyntax(searchPathElementsArray, numberOfLevels) {
  106. var pathString = "";
  107. for (var i = 1; i < numberOfLevels; i++) {
  108. var folderName = searchPathElementsArray[i].replace('namespaceFolder[@name=\"', '');
  109. folderName = folderName.replace('\"]', '');
  110. folderName = folderName.replace('namespaceFolder[@name=\'', '');
  111. folderName = folderName.replace('\']', '');
  112. pathString = pathString + "/" + folderName;
  113. }
  114. return pathString;
  115. },
  116. bindEvents: function bindEvents() {
  117. this.$el.on('com.ibm.admin.listItemHighlighted', function (e, obj) {
  118. if (obj.type === 'namespaceFolder') {
  119. var highlightedDestination = this.explorerView.getSelectedObjects();
  120. this._renderDestinationLebel(obj.defaultName);
  121. this.targetLocation = highlightedDestination[0];
  122. }
  123. }.bind(this));
  124. this.explorerView.listAdaptor.on('pathChanged', function (path) {
  125. var $destinationEl = $('.copy-move-destinantion-label');
  126. if (!$destinationEl.length) {
  127. $destinationEl = $($.parseHTML('<div class="copy-move-destinantion-label adm-item-title"></div>'));
  128. }
  129. if (path.length === 1) {
  130. if ($destinationEl.length) {
  131. $destinationEl.text('');
  132. }
  133. this.dialog.disableButton('copy');
  134. this.dialog.disableButton('move');
  135. } else if (path.length === 2) {
  136. this.dialog.enableButton('copy');
  137. this.dialog.enableButton('move');
  138. var destination = StringResource.get('destination') + ' ' + _.last(path).defaultName;
  139. $destinationEl.text(destination);
  140. this.$('.bi-admin-account-list.bi-admin-flexcontent').after($destinationEl[0]);
  141. this.targetLocation = _.last(path);
  142. } else if (path.length > 2) {
  143. this._renderDestinationLebel(_.last(path).defaultName);
  144. this.targetLocation = _.last(path);
  145. }
  146. if (this.getPathStringFromTargetLoc() === this.getPathStringFromObjectSearchPath(this.inputObjects[0]) || this._isTargetDirectoryRoot(path) || this._isSourceAndTargetBothCognosOrTenantRoot(this.inputObjects[0].ancestors)) {
  147. this.dialog.disableButton('move');
  148. } else {
  149. this.dialog.enableButton('move');
  150. }
  151. }.bind(this));
  152. },
  153. /**
  154. * Determines if both source and target are the same location when it comes to Cognos root, i.e.:
  155. * Cognos root/ tenant ns root to Cognos root/ tenant ns root
  156. * @param {*} ancestors ancesters of source object
  157. * @returns {boolean}
  158. */
  159. _isSourceAndTargetBothCognosOrTenantRoot: function _isSourceAndTargetBothCognosOrTenantRoot(ancestors) {
  160. var targetFolderLocationArray = this.targetLocation.searchPath.split('/');
  161. if (targetFolderLocationArray.length !== 1 && this.targetLocation.id !== 'xOg__') {
  162. //Means we are not trying to move to Cognos root or tenant ns folder root
  163. return false;
  164. }
  165. var ancestor = ancestors[ancestors.length - 1];
  166. if (ancestor.id === this.explorerView._accountExplorer.tenantNsFolderId) {
  167. return true;
  168. }
  169. return false;
  170. },
  171. /**
  172. * Determines if target is directory root
  173. * @param {*} path - array of folders split for target searchPath
  174. * @returns {boolean}
  175. */
  176. _isTargetDirectoryRoot: function _isTargetDirectoryRoot(path) {
  177. //Don't show if in Directory root location
  178. if (path.length === 1 && path[0].type === 'directory') {
  179. return true;
  180. }
  181. return false;
  182. },
  183. //Called by the copy dialog when it asks the view for the button spec
  184. getButtonSpec: function getButtonSpec(options) {
  185. var spec = [];
  186. if (options.viewOptions.type === 'copyMoveDialog') {
  187. spec.push({
  188. 'text': StringResource.get('copyTo'),
  189. 'handler': this.copy.bind(this),
  190. 'type': 'primary copy',
  191. 'defaultId': 'copyButton'
  192. }, {
  193. 'text': StringResource.get('moveTo'),
  194. 'handler': this.move.bind(this),
  195. 'type': 'primary move',
  196. 'defaultId': 'moveButton'
  197. });
  198. }
  199. spec.push('cancel');
  200. return spec;
  201. },
  202. _getURLForCopyMoveRequest: function _getURLForCopyMoveRequest(operationType, sourceId, targetId) {
  203. var url;
  204. if (operationType === 'copy') {
  205. url = 'v1/namespaces/' + targetId + '/items?recursive=true&source=' + sourceId;
  206. } else if (operationType === 'move') {
  207. // for move, we have to check for tenantNamespaceFolder when moving objects to xOg__
  208. // For copy this is taken care of by the server:
  209. // https://github.ibm.com/BusinessAnalytics/ba-admin/blob/8dc2760c1f7b5cffe9ab42356ac32571a095f1ae/
  210. // admin-common-service/src/main/java/com/ibm/bi/admin/commons/filters/CognosNsPathOverrideFilter.java#L65
  211. if (targetId === 'xOg__' && this.explorerView._accountExplorer.tenantNsFolderId) {
  212. targetId = this.explorerView._accountExplorer.tenantNsFolderId;
  213. }
  214. url = 'v1/namespaces/' + sourceId + '?parent=' + targetId;
  215. }
  216. return url;
  217. },
  218. _doInitialCopyMoveRequests: function _doInitialCopyMoveRequests(operationType, sourceId, targetI, url) {
  219. var actionPromise = {};
  220. if (operationType === 'copy') {
  221. actionPromise = this.makeCopyRequest(url);
  222. } else if (operationType === 'move') {
  223. actionPromise = this.makeMoveRequest(url);
  224. }
  225. return actionPromise;
  226. },
  227. copy: function copy(a) {
  228. var aPromises = this._handleCopyMoveRequests('copy');
  229. Q.allSettled(aPromises).then(function (r) {
  230. this.dialog.remove();
  231. this.glassContext.appController.showToast(StringResource.get('copySuccess'), {
  232. 'type': 'success'
  233. });
  234. this.parent.refresh();
  235. }.bind(this), function (e) {
  236. this.dialog.remove();
  237. this.glassContext.appController.showToast(StringResource.get('copyError'), {
  238. 'type': 'error'
  239. });
  240. }.bind(this));
  241. },
  242. _replace: function _replace(url, index) {
  243. if (this.operationType === 'copy') {
  244. return this.makeCopyRequest(url + '&updateAction=replace').then(function (response) {
  245. this.trackPromises[index].resolve();
  246. }.bind(this));
  247. } else if (this.operationType === 'move') {
  248. return this.makeMoveRequest(url + '&updateAction=replace').then(function (response) {
  249. this.trackPromises[index].resolve();
  250. }.bind(this));
  251. }
  252. },
  253. _handleNameConflict: function _handleNameConflict(error, url, count, defaultName, index) {
  254. if (error.requestInfo.jqXHR.responseJSON.errors[0].errorCode === 'cmNameConflict') {
  255. count = count + 1;
  256. this._keepBoth(count, url, defaultName, index);
  257. }
  258. },
  259. _keepBoth: function _keepBoth(count, url, defaultName, index) {
  260. this.count = count;
  261. /*Keeps both objects in the collision by renaming the new object*/
  262. if (this.operationType === 'copy') {
  263. this.makeCopyRenameRequest(url, count, defaultName).then(function (response) {
  264. this.trackPromises[index].resolve();
  265. }.bind(this), function (error) {
  266. this._handleNameConflict(error, url, count, defaultName, index);
  267. }.bind(this));
  268. } else if (this.operationType === 'move') {
  269. this.makeMoveRenameRequest(url, count, defaultName).then(function (response) {
  270. this.trackPromises[index].resolve();
  271. }.bind(this), function (error) {
  272. this._handleNameConflict(error, url, count, defaultName, index);
  273. }.bind(this));
  274. }
  275. },
  276. _skip: function _skip() {
  277. /*Closes the dialog without doing anything*/
  278. var deferred = Q.defer();
  279. deferred.resolve();
  280. return deferred.promise;
  281. },
  282. _handleCopyMoveRequests: function _handleCopyMoveRequests(operationType) {
  283. this.operationType = operationType;
  284. var executionPromises = [];
  285. var itemsSelected = this.inputObjects;
  286. this.trackPromises = [];
  287. _.each(itemsSelected, function (item, index, list) {
  288. var url = this._getURLForCopyMoveRequest(operationType, item.id, this.targetLocation.id);
  289. executionPromises[index] = this._doInitialCopyMoveRequests(operationType, item.id, this.targetLocation.id, url);
  290. this.trackPromises[index] = _.clone(executionPromises[index]);
  291. this.trackPromises[index] = Q.defer();
  292. this.trackPromises[index].itemId = item.id;
  293. this.trackPromises[index].defaultName = item.defaultName;
  294. this.trackPromises[index].url = url;
  295. }.bind(this));
  296. Promise.all(executionPromises.map(function (promise) {
  297. return promise.reflect();
  298. })).each(function (promise, index, length) {
  299. if (promise.isFulfilled()) {
  300. this.trackPromises[index].resolve();
  301. } else {
  302. var errorResponse = promise.reason().jqXHR.responseJSON;
  303. if (errorResponse.errors[0].errorCode === 'cmNameConflict' || this.getPathStringFromTargetLoc() === this.getPathStringFromObjectSearchPath(this.inputObjects[0])) {
  304. var nCDialog = new NameCollisionDialog('nameCollision', StringResource.get('nameCollision', {
  305. 'nameOfItem': this.targetLocation.defaultName
  306. }), StringResource.get('nameCollisionMessage'));
  307. var itemUrl = this.trackPromises[index].url;
  308. nCDialog.create(function () {
  309. this._replace(itemUrl, index);
  310. }.bind(this), function () {
  311. this._keepBoth(1, itemUrl, this.trackPromises[index].defaultName, index);
  312. }.bind(this), this._skip.bind(this), 'copy', this.inputObjects[0].defaultName);
  313. nCDialog.open();
  314. } else {
  315. this.glassContext.appController.showErrorMessage(errorResponse.errors[0].messages[0], StringResource.get('copyError'));
  316. this.trackPromises[index].resolve();
  317. }
  318. }
  319. }.bind(this));
  320. var pObjs = [];
  321. _.each(this.trackPromises, function (item, index, list) {
  322. pObjs[index] = item.promise;
  323. }.bind(this));
  324. return pObjs;
  325. },
  326. move: function move(a) {
  327. var aPromises = this._handleCopyMoveRequests('move');
  328. Q.allSettled(aPromises).then(function (r) {
  329. this._refreshAccountListPane();
  330. this.dialog.remove();
  331. this.glassContext.appController.showToast(StringResource.get('moveSuccess'), {
  332. 'type': 'success'
  333. });
  334. this.parent.refresh();
  335. }.bind(this), function (e) {
  336. this.dialog.remove();
  337. this.glassContext.appController.showToast(StringResource.get('moveError'), {
  338. 'type': 'error'
  339. });
  340. }.bind(this));
  341. },
  342. makeCopyRequest: function makeCopyRequest(url) {
  343. return this.glassContext.services.fetch.post(url, {
  344. contentType: 'application/json; charset=utf-8'
  345. });
  346. },
  347. makeCopyRenameRequest: function makeCopyRenameRequest(url, count, defaultName) {
  348. var newName = defaultName + ' (' + count + ')';
  349. var newUrl = url + '&defaultName=' + newName;
  350. return this.glassContext.services.fetch.post(newUrl, {
  351. contentType: 'application/json; charset=utf-8'
  352. });
  353. },
  354. makeMoveRenameRequest: function makeMoveRenameRequest(url, count, defaultName) {
  355. var newName = defaultName + ' (' + count + ')';
  356. var newUrl = url + '&defaultName=' + newName;
  357. return this.glassContext.services.fetch.put(newUrl, {
  358. contentType: 'application/json; charset=utf-8'
  359. });
  360. },
  361. makeMoveRequest: function makeMoveRequest(url) {
  362. return this.glassContext.services.fetch.put(url);
  363. },
  364. _refreshAccountListPane: function _refreshAccountListPane() {
  365. this.parent.listAdaptor.sortChanged = true;
  366. this.parent.listView.reload(false);
  367. }
  368. });
  369. return SecurityObjectExplorerCopyMoveView;
  370. });