manage_alert_groups_controller.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. var validator = require('utils/validator');
  20. var numberUtils = require('utils/number_utils');
  21. App.ManageAlertGroupsController = Em.Controller.extend({
  22. name: 'manageAlertGroupsController',
  23. isLoaded: false,
  24. alertGroups: [],
  25. originalAlertGroups: [],
  26. selectedAlertGroup: null,
  27. selectedDefinitions: [],
  28. alertDefinitions: [],
  29. // load alert definitions for all services
  30. loadAlertDefinitions: function () {
  31. App.ajax.send({
  32. name: 'alerts.load_all_alert_definitions',
  33. sender: this,
  34. success: 'onLoadAlertDefinitionsSuccess',
  35. error: 'onLoadAlertDefinitionsError'
  36. });
  37. },
  38. onLoadAlertDefinitionsSuccess: function (data) {
  39. var alertDefinitions = [];
  40. if (data && data.items) {
  41. data.items.forEach( function(def) {
  42. if (def && def.AlertDefinition) {
  43. alertDefinitions.pushObject (Em.Object.create({
  44. name: def.AlertDefinition.name,
  45. serviceName: def.AlertDefinition.service_name,
  46. serviceNameDisplay:function() {
  47. return App.format.role(this.get('serviceName'));
  48. }.property('serviceName'),
  49. componentName: def.AlertDefinition.component_name,
  50. componentNameDisplay: function() {
  51. return App.format.role(this.get('componentName'));
  52. }.property('componentName'),
  53. label: def.AlertDefinition.label,
  54. id: def.AlertDefinition.id
  55. }));
  56. }
  57. });
  58. }
  59. this.set('alertDefinitions', alertDefinitions);
  60. },
  61. onLoadAlertDefinitionsError: function () {
  62. console.error('Unable to load all alert definitions.');
  63. },
  64. loadAlertGroups: function () {
  65. this.set('isLoaded', false);
  66. this.set('alertGroups.length', 0);
  67. this.set('originalAlertGroups.length', 0);
  68. App.ajax.send({
  69. name: 'alerts.load_alert_groups',
  70. sender: this,
  71. success: 'onLoadAlertGroupsSuccess',
  72. error: 'onLoadAlertGroupsError'
  73. });
  74. },
  75. onLoadAlertGroupsSuccess: function (data) {
  76. var self = this;
  77. if (data && data.items) {
  78. this.set('alertGroupsCount', data.items.length);
  79. data.items.forEach(function(alert_group) {
  80. App.ajax.send({
  81. name: 'alerts.load_an_alert_group',
  82. sender: self,
  83. data: {
  84. "group_id": alert_group.AlertGroup.id
  85. },
  86. success: 'onLoadAlertGroupSuccess',
  87. error: 'onLoadAlertGroupError'
  88. });
  89. }, this);
  90. }
  91. },
  92. onLoadAlertGroupSuccess: function (data) {
  93. var alertGroups = this.get('alertGroups');
  94. if (data && data.AlertGroup) {
  95. alertGroups.pushObject ( App.AlertGroupComplex.create({
  96. name: data.AlertGroup.name,
  97. default: data.AlertGroup.default,
  98. id: data.AlertGroup.id,
  99. definitions: data.AlertGroup.definitions,
  100. targets: data.AlertGroup.targets
  101. }));
  102. }
  103. if (this.get('alertGroupsCount') == alertGroups.length) {
  104. this.set('isLoaded', true);
  105. this.set('originalAlertGroups', this.copyAlertGroups(alertGroups));
  106. }
  107. },
  108. onLoadAlertGroupsError: function () {
  109. console.error('Unable to load all alert groups.');
  110. },
  111. onLoadAlertGroupError: function () {
  112. console.error('Unable to load an alert group.');
  113. },
  114. resortAlertGroup: function() {
  115. var alertGroups = Ember.copy(this.get('alertGroups'));
  116. if(alertGroups.length < 2){
  117. return;
  118. }
  119. var defaultGroups = alertGroups.filterProperty('default');
  120. defaultGroups.forEach( function(defaultGroup) {
  121. alertGroups.removeObject(defaultGroup);
  122. });
  123. var sorted = defaultGroups.concat(alertGroups.sortProperty('name'));
  124. // var sorted = alertGroups.sortProperty('name');
  125. this.removeObserver('alertGroups.@each.name', this, 'resortAlertGroup');
  126. this.set('alertGroups', sorted);
  127. this.addObserver('alertGroups.@each.name', this, 'resortAlertGroup');
  128. }.observes('alertGroups.@each.name'),
  129. /**
  130. * remove definitions from group
  131. */
  132. deleteDefinitions: function () {
  133. if (this.get('isDeleteDefinitionsDisabled')) {
  134. return;
  135. }
  136. var groupDefinitions = this.get('selectedAlertGroup.definitions');
  137. this.get('selectedDefinitions').slice().forEach(function (defObj) {
  138. groupDefinitions.removeObject(defObj);
  139. }, this);
  140. this.set('selectedDefinitions', []);
  141. },
  142. isDeleteDefinitionsDisabled: function () {
  143. var selectedGroup = this.get('selectedAlertGroup');
  144. if (selectedGroup) {
  145. return selectedGroup.default || this.get('selectedDefinitions').length === 0;
  146. }
  147. return true;
  148. }.property('selectedAlertGroup', 'selectedAlertGroup.definitions.length', 'selectedDefinitions.length'),
  149. /**
  150. * add alert definitions to a group
  151. */
  152. addDefinitions: function () {
  153. if (this.get('selectedAlertGroup.isAddDefinitionsDisabled')){
  154. return false;
  155. }
  156. var availableDefinitions = this.get('selectedAlertGroup.availableDefinitions');
  157. var popupDescription = {
  158. header: Em.I18n.t('alerts.actions.manage_alert_groups_popup.selectDefsDialog.title'),
  159. dialogMessage: Em.I18n.t('alerts.actions.manage_alert_groups_popup.selectDefsDialog.message').format(this.get('selectedAlertGroup.displayName'))
  160. };
  161. var validComponents = App.StackServiceComponent.find().map(function (component) {
  162. return Em.Object.create({
  163. componentName: component.get('componentName'),
  164. displayName: App.format.role(component.get('componentName')),
  165. selected: false
  166. });
  167. });
  168. var validServices = App.Service.find().map(function (service) {
  169. return Em.Object.create({
  170. serviceName: service.get('serviceName'),
  171. displayName: App.format.role(service.get('serviceName')),
  172. selected: false
  173. });
  174. });
  175. this.launchDefsSelectionDialog (availableDefinitions, [], validServices, validComponents, this.addDefinitionsCallback.bind(this), popupDescription);
  176. },
  177. /**
  178. * lauch a table view of all availavle definitions to choose
  179. */
  180. launchDefsSelectionDialog : function(initialDefs, selectedDefs, validServices, validComponents, callback, popupDescription) {
  181. App.ModalPopup.show({
  182. classNames: [ 'sixty-percent-width-modal' ],
  183. header: popupDescription.header,
  184. dialogMessage: popupDescription.dialogMessage,
  185. warningMessage: null,
  186. availableDefs: [],
  187. onPrimary: function () {
  188. this.set('warningMessage', null);
  189. var arrayOfSelectedDefs = this.get('availableDefs').filterProperty('selected', true);
  190. if (arrayOfSelectedDefs.length < 1) {
  191. this.set('warningMessage', Em.I18n.t('alerts.actions.manage_alert_groups_popup.selectDefsDialog.message.warning'));
  192. return;
  193. }
  194. callback(arrayOfSelectedDefs);
  195. console.debug('(new-selectedDefs)=', arrayOfSelectedDefs);
  196. this.hide();
  197. },
  198. disablePrimary: function () {
  199. return !this.get('isLoaded');
  200. }.property('isLoaded'),
  201. onSecondary: function () {
  202. callback(null);
  203. this.hide();
  204. },
  205. bodyClass: App.TableView.extend({
  206. templateName: require('templates/main/alerts/add_definition_to_group_popup'),
  207. controllerBinding: 'App.router.manageAlertGroupsController',
  208. isPaginate: true,
  209. filteredContent: function() {
  210. return this.get('parentView.availableDefs').filterProperty('filtered') || [];
  211. }.property('parentView.availableDefs.@each.filtered'),
  212. showOnlySelectedDefs: false,
  213. filterComponents: validComponents,
  214. filterServices: validServices,
  215. filterComponent: null,
  216. filterService: null,
  217. isDisabled: function () {
  218. return !this.get('parentView.isLoaded');
  219. }.property('parentView.isLoaded'),
  220. didInsertElement: function(){
  221. initialDefs.setEach('filtered', true);
  222. this.set('parentView.availableDefs', initialDefs);
  223. this.set('parentView.isLoaded', true);
  224. },
  225. filterDefs: function () {
  226. var showOnlySelectedDefs = this.get('showOnlySelectedDefs');
  227. var filterComponent = this.get('filterComponent');
  228. var filterService = this.get('filterService');
  229. this.get('parentView.availableDefs').forEach(function (defObj) {
  230. var componentOnObj = true;
  231. var serviceOnObj = true;
  232. if (filterComponent) {
  233. componentOnObj = (defObj.componentName == filterComponent.get('componentName'));
  234. }
  235. if (defObj.serviceName && filterService) {
  236. serviceOnObj = (defObj.serviceName == filterService.get('serviceName'));
  237. }
  238. if (showOnlySelectedDefs) {
  239. defObj.set('filtered', componentOnObj && serviceOnObj && defObj.get('selected'));
  240. } else {
  241. defObj.set('filtered', componentOnObj && serviceOnObj);
  242. }
  243. }, this);
  244. this.set('startIndex', 1);
  245. }.observes('parentView.availableDefs', 'filterService', 'filterService.serviceName', 'filterComponent', 'filterComponent.componentName', 'showOnlySelectedDefs'),
  246. defSelectMessage: function () {
  247. var defs = this.get('parentView.availableDefs');
  248. var selectedDefs = defs.filterProperty('selected', true);
  249. return this.t('alerts.actions.manage_alert_groups_popup.selectDefsDialog.selectedDefsLink').format(selectedDefs.get('length'), defs.get('length'));
  250. }.property('parentView.availableDefs.@each.selected'),
  251. selectFilterComponent: function (event) {
  252. if (event != null && event.context != null && event.context.componentName != null) {
  253. var currentFilter = this.get('filterComponent');
  254. if (currentFilter != null) {
  255. currentFilter.set('selected', false);
  256. }
  257. if (currentFilter != null && currentFilter.componentName === event.context.componentName) {
  258. // selecting the same filter deselects it.
  259. this.set('filterComponent', null);
  260. } else {
  261. this.set('filterComponent', event.context);
  262. event.context.set('selected', true);
  263. }
  264. }
  265. },
  266. selectFilterService: function (event) {
  267. if (event != null && event.context != null && event.context.serviceName != null) {
  268. var currentFilter = this.get('filterService');
  269. if (currentFilter != null) {
  270. currentFilter.set('selected', false);
  271. }
  272. if (currentFilter != null && currentFilter.serviceName === event.context.serviceName) {
  273. // selecting the same filter deselects it.
  274. this.set('filterService', null);
  275. } else {
  276. this.set('filterService', event.context);
  277. event.context.set('selected', true);
  278. }
  279. }
  280. },
  281. allDefsSelected: false,
  282. toggleSelectAllDefs: function (event) {
  283. this.get('parentView.availableDefs').filterProperty('filtered').setEach('selected', this.get('allDefsSelected'));
  284. }.observes('allDefsSelected'),
  285. toggleShowSelectedDefs: function () {
  286. var filter1 = this.get('filterComponent');
  287. if (filter1 != null) {
  288. filter1.set('selected', false);
  289. }
  290. var filter2 = this.get('filterService');
  291. if (filter2 != null) {
  292. filter2.set('selected', false);
  293. }
  294. this.set('filterComponent', null);
  295. this.set('filterService', null);
  296. this.set('showOnlySelectedDefs', !this.get('showOnlySelectedDefs'));
  297. }
  298. })
  299. });
  300. },
  301. /**
  302. * add alert definitions callback
  303. */
  304. addDefinitionsCallback: function (selectedDefs) {
  305. var group = this.get('selectedAlertGroup');
  306. if (selectedDefs) {
  307. var alertGroupDefs = group.get('definitions');
  308. selectedDefs.forEach(function (defObj) {
  309. alertGroupDefs.pushObject(defObj);
  310. }, this);
  311. }
  312. },
  313. /**
  314. * observes if any group changed including: group name, newly created group, deleted group, group with definitions changed
  315. */
  316. defsModifiedAlertGroups: function () {
  317. if (!this.get('isLoaded')) {
  318. return false;
  319. }
  320. var groupsToDelete = [];
  321. var groupsToSet = [];
  322. var groupsToCreate = [];
  323. var groups = this.get('alertGroups'); //current alert groups
  324. var originalGroups = this.get('originalAlertGroups'); // original alert groups
  325. // remove default group
  326. originalGroups = originalGroups.filterProperty('default', false);
  327. var originalGroupsIds = originalGroups.mapProperty('id');
  328. groups.forEach(function (group) {
  329. if (!group.get('default')) {
  330. var originalGroup = originalGroups.findProperty('id', group.get('id'));
  331. if (originalGroup) {
  332. if (!(JSON.stringify(group.get('definitions').slice().sort()) === JSON.stringify(originalGroup.get('definitions').slice().sort()))
  333. || !(JSON.stringify(group.get('targets').slice().sort()) === JSON.stringify(originalGroup.get('targets').slice().sort()))) {
  334. groupsToSet.push(group.set('id', originalGroup.get('id')));
  335. } else if (group.get('name') !== originalGroup.get('name') ) {
  336. // should update name
  337. groupsToSet.push(group.set('id', originalGroup.get('id')));
  338. }
  339. originalGroupsIds = originalGroupsIds.without(group.get('id'));
  340. } else {
  341. groupsToCreate.push(group);
  342. }
  343. }
  344. });
  345. originalGroupsIds.forEach(function (id) {
  346. groupsToDelete.push(originalGroups.findProperty('id', id));
  347. }, this);
  348. return {
  349. toDelete: groupsToDelete,
  350. toSet: groupsToSet,
  351. toCreate: groupsToCreate
  352. };
  353. }.property('selectedAlertGroup.definitions.@each', 'selectedAlertGroup.definitions.length', 'alertGroups', 'isLoaded'),
  354. isDefsModified: function () {
  355. var modifiedGroups = this.get('defsModifiedAlertGroups');
  356. if (!this.get('isLoaded')) {
  357. return false;
  358. }
  359. return !!(modifiedGroups.toSet.length || modifiedGroups.toCreate.length || modifiedGroups.toDelete.length);
  360. }.property('defsModifiedAlertGroups'),
  361. /**
  362. * copy alert groups for backup, to compare with current alert groups, so will know if some groups changed/added/deleted
  363. * @param originGroups
  364. * @return {Array}
  365. */
  366. copyAlertGroups: function (originGroups) {
  367. var alertGroups = [];
  368. originGroups.forEach(function (alertGroup) {
  369. var copiedGroup = App.AlertGroupComplex.create($.extend(true, {}, alertGroup));
  370. alertGroups.pushObject(copiedGroup);
  371. });
  372. return alertGroups;
  373. },
  374. /**
  375. * ==============on API side: following are four functions to an alert group: create, delete, update definitions/targets/label of a group ===========
  376. */
  377. /**
  378. * Create a new alert group
  379. * @param newAlertGroupData
  380. * @param callback Callback function for Success or Error handling
  381. * @return Returns the created alert group
  382. */
  383. postNewAlertGroup: function (newAlertGroupData, callback) {
  384. // create a new group with name , definition and target
  385. var data = {
  386. 'name': newAlertGroupData.get('name')
  387. };
  388. if (newAlertGroupData.get('definitions').length > 0) {
  389. data.definitions = newAlertGroupData.get('definitions').mapProperty('id');
  390. }
  391. if (newAlertGroupData.get('targets').length > 0) {
  392. data.targets = newAlertGroupData.get('targets').mapProperty('id');
  393. }
  394. var sendData = {
  395. name: 'alert_groups.create',
  396. data: data,
  397. success: 'successFunction',
  398. error: 'errorFunction',
  399. successFunction: function () {
  400. if (callback) {
  401. callback();
  402. }
  403. },
  404. errorFunction: function (xhr, text, errorThrown) {
  405. if (callback) {
  406. callback(xhr, text, errorThrown);
  407. }
  408. console.error('Error in creating new Alert Group');
  409. }
  410. };
  411. sendData.sender = sendData;
  412. App.ajax.send(sendData);
  413. return newAlertGroupData;
  414. },
  415. /**
  416. * PUTs the new alert group information on the server.
  417. * Changes possible here are the name, definitions, targets
  418. *
  419. * @param {App.ConfigGroup} alertGroup
  420. * @param {Function} successCallback
  421. * @param {Function} errorCallback
  422. */
  423. updateAlertGroup: function (alertGroup, successCallback, errorCallback) {
  424. var sendData = {
  425. name: 'alert_groups.update',
  426. data: {
  427. "group_id": alertGroup.id,
  428. 'name': alertGroup.get('name'),
  429. 'definitions': alertGroup.get('definitions').mapProperty('id'),
  430. 'targets': alertGroup.get('targets').mapProperty('id')
  431. },
  432. success: 'successFunction',
  433. error: 'errorFunction',
  434. successFunction: function () {
  435. if (successCallback) {
  436. successCallback();
  437. }
  438. },
  439. errorFunction: function (xhr, text, errorThrown) {
  440. if (errorCallback) {
  441. errorCallback(xhr, text, errorThrown);
  442. }
  443. }
  444. };
  445. sendData.sender = sendData;
  446. App.ajax.send(sendData);
  447. },
  448. removeAlertGroup: function (alertGroup, successCallback, errorCallback) {
  449. var sendData = {
  450. name: 'alert_groups.delete',
  451. data: {
  452. "group_id": alertGroup.id
  453. },
  454. success: 'successFunction',
  455. error: 'errorFunction',
  456. successFunction: function () {
  457. if (successCallback) {
  458. successCallback();
  459. }
  460. },
  461. errorFunction: function (xhr, text, errorThrown) {
  462. if (errorCallback) {
  463. errorCallback(xhr, text, errorThrown);
  464. }
  465. }
  466. };
  467. sendData.sender = sendData;
  468. App.ajax.send(sendData);
  469. },
  470. /**
  471. * =============on UI side: following are four operations to an alert group: add, remove, rename and duplicate==================
  472. */
  473. /**
  474. * confirm delete alert group
  475. */
  476. confirmDelete : function () {
  477. var self = this;
  478. App.showConfirmationPopup(function() {
  479. self.deleteAlertGroup();
  480. });
  481. },
  482. /**
  483. * delete selected alert group
  484. */
  485. deleteAlertGroup: function () {
  486. var selectedAlertGroup = this.get('selectedAlertGroup');
  487. if (this.get('isDeleteAlertDisabled')) {
  488. return;
  489. }
  490. this.get('alertGroups').removeObject(selectedAlertGroup);
  491. this.set('selectedAlertGroup', this.get('alertGroups')[0]);
  492. },
  493. /**
  494. * rename non-default alert group
  495. */
  496. renameAlertGroup: function () {
  497. if(this.get('selectedAlertGroup.default')) {
  498. return;
  499. }
  500. var self = this;
  501. this.renameGroupPopup = App.ModalPopup.show({
  502. header: Em.I18n.t('alerts.actions.manage_alert_groups_popup.renameButton'),
  503. bodyClass: Ember.View.extend({
  504. templateName: require('templates/main/alerts/create_new_alert_group')
  505. }),
  506. alertGroupName: self.get('selectedAlertGroup.name'),
  507. warningMessage: null,
  508. validate: function () {
  509. var warningMessage = '';
  510. var originalGroup = self.get('selectedAlertGroup');
  511. var groupName = this.get('alertGroupName').trim();
  512. if (originalGroup.get('name').trim() === groupName) {
  513. warningMessage = Em.I18n.t("alerts.actions.manage_alert_groups_popup.addGroup.exist");
  514. } else {
  515. if (self.get('alertGroups').mapProperty('displayName').contains(groupName)) {
  516. warningMessage = Em.I18n.t("alerts.actions.manage_alert_groups_popup.addGroup.exist");
  517. }
  518. else if (groupName && !validator.isValidAlertGroupName(groupName)) {
  519. warningMessage = Em.I18n.t("form.validator.alertGroupName");
  520. }
  521. }
  522. this.set('warningMessage', warningMessage);
  523. }.observes('alertGroupName'),
  524. disablePrimary: function () {
  525. return !(this.get('alertGroupName').trim().length > 0 && (this.get('warningMessage') !== null && !this.get('warningMessage')));
  526. }.property('warningMessage', 'alertGroupName'),
  527. onPrimary: function () {
  528. self.set('selectedAlertGroup.name', this.get('alertGroupName'));
  529. this.hide();
  530. }
  531. });
  532. },
  533. /**
  534. * create new alert group
  535. */
  536. addAlertGroup: function (duplicated) {
  537. duplicated = (duplicated === true);
  538. var self = this;
  539. this.addGroupPopup = App.ModalPopup.show({
  540. header: Em.I18n.t('alerts.actions.manage_alert_groups_popup.addButton'),
  541. bodyClass: Ember.View.extend({
  542. templateName: require('templates/main/alerts/create_new_alert_group')
  543. }),
  544. alertGroupName: duplicated ? self.get('selectedAlertGroup.name') + ' Copy' : "",
  545. warningMessage: '',
  546. didInsertElement: function(){
  547. this.validate();
  548. this.$('input').focus();
  549. },
  550. validate: function () {
  551. var warningMessage = '';
  552. var groupName = this.get('alertGroupName').trim();
  553. if (self.get('alertGroups').mapProperty('displayName').contains(groupName)) {
  554. warningMessage = Em.I18n.t("alerts.actions.manage_alert_groups_popup.addGroup.exist");
  555. }
  556. else if (groupName && !validator.isValidAlertGroupName(groupName)) {
  557. warningMessage = Em.I18n.t("form.validator.alertGroupName");
  558. }
  559. this.set('warningMessage', warningMessage);
  560. }.observes('alertGroupName'),
  561. disablePrimary: function () {
  562. return !(this.get('alertGroupName').trim().length > 0 && !this.get('warningMessage'));
  563. }.property('warningMessage', 'alertGroupName'),
  564. onPrimary: function () {
  565. var newAlertGroup = App.AlertGroupComplex.create({
  566. name: this.get('alertGroupName').trim(),
  567. definitions: [],
  568. targets: []
  569. }) ;
  570. self.get('alertGroups').pushObject(newAlertGroup);
  571. this.hide();
  572. }
  573. });
  574. },
  575. duplicateAlertGroup: function() {
  576. this.addAlertGroup(true);
  577. }
  578. });